# js对象

以前的对象遍历是没有特定的顺序的。

枚举顺序:ES6 规定了自有属性的枚举顺序,并且会将同一类别的属性整合到一块,具体的排列规则如下所列。

1)首先遍历数字类型或数字字符串的属性,按大小升序排列。

2)接着遍历字符串类型的属性,按添加时间的先后顺序排列。

3)最后遍历符号类型的属性,也按添加顺序排列。

var obj = {
 c: 1,
 1: 2,
 a: 3,
 "0": 4,
 [Symbol("x")]: 5,
 [Symbol("y")]: 6
};
var properties = [];
for(var key in obj) {
 if(obj.hasOwnProperty(key)) { //过滤掉继承属性
 properties.push(key);
 }
}
console.log(properties); //["0", "1", "c", "a"]
JSON.stringify(obj); //{"0":4,"1":2,"c":1,"a":3}
Object.getOwnPropertyNames(obj); //["0", "1", "c", "a"]
Object.keys(obj); //["0", "1", "c", "a"]
Object.getOwnPropertySymbols(obj); //[Symbol(x), Symbol(y)]
Object.assign({}, obj); //{0: 4, 1: 2, c: 1, a: 3, Symbol(x): 5, Symbol(y): 6}

# 关于对象的遍历

  • object for-in 循环循环自身和原型上的属性,不包括Symbol如果该属性设置了不可遍历,则会忽略
  • 使用 obj3=Object.create(obj1,obj2) ,obj1是在原型上,obj2是添加到obj3中,obj2设置的值 默认不支持遍历 的除非手动enumerable:true
let obj=Object.create({a:1},{b:{value:17},c:{value:20,enumerable:true}})
	
for(let i in obj){
	console.log(i)
}
//只有 c 和 a

如果原型上的a设置了enumerable:false,依然是可以遍历的!因为第二个参数之后才符合Object.definePerporty规则

let obj=Object.create({a:{value:1000,enumerable:false}},{b:{value:17},c:{value:20,enumerable:true}})
	
for(let i in obj){
	console.log(i)
}
// c
// a
console.warn(obj['a'])//{value: 1000, enumerable: false}
consolo.warn(obj['c'])//20
  • Object.entries(obj),Object.keys(obj),Object.values(obj)只会给出对应的obj 自身的可遍历属性
  • Object.getOwnPropertyDescriptor(obj,'x'),查出自身的属性的具体信息:value,
let obj=Object.create({a:1},{b:{value:17},c:{value:20,enumerable:true}})
console.log(Object.getOwnPropertyDescriptor(obj,'b'))
console.log(Object.getOwnPropertyNames(obj))
//{value: 17, writable: false, enumerable: false, configurable: false}
// ['b', 'c']
  • Object.getOwnPropertyNames(obj) :只获取对象自身的属性的key值,包括可遍历的和不可遍历的属性

遍历关于symbol

如果响应的属性key是Symbol设置的,那么是无法通过以上方法遍历的 ,可借助Object.getOwnPropertySymbols获取属性是symbol的, 也可以使用Object.getOwnPropertyDescriptors() 不过只能查看无法遍历到symbol类型

let obj={name:'jspang',skill:'web'};
//let obj={name:'jspang',skill:'web',c:{value:20,enumerable:false}};
// 这种写法会认为c有两个参数而已,Object.keys依然能遍历到c

Object.defineProperty(obj, "c", {
    value: "c++",
    //是否为枚举属性
    enumerable: false
});
let age=Symbol();
let t1 =Symbol('t1')
obj[age]=18;
obj[t1]='t111'
obj.sex="man"
console.log(Object.getOwnPropertyNames(obj))
//['name', 'skill', 'c', 'sex']
console.log(Object.keys(obj))//['name', 'skill', 'sex']
console.log(obj.c)//c++
console.log(obj[age])//18

console.log(Object.getOwnPropertySymbols(obj))// [Symbol(), Symbol(t1)]
Object.getOwnPropertySymbols(obj).forEach(el=>{
	console.log(obj[el])
	//18
	// t111
})
console.log(Object.getOwnPropertyDescriptors(obj))// 见下图
for (let i in Object.getOwnPropertyDescriptors(obj)){
	console.log(i)
	// 不包括Symbol
}
console.log(age in obj)//true

# 属性枚举顺序

  • for-in 循环、Object.keys()、Object.getOwnPropertyNames()、Object.getOwnProperty- Symbols()以及 Object.assign()在属性枚举顺序方面有很大区别。
  • for-in 循环和 Object.keys() 的枚举顺序是不确定的。
  • Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign() 的枚举顺序是确定性的。先以升序枚举数值键,然后以插入顺序枚举字符串和符号键。在对象字面量中 定义的键以它们逗号分隔的顺序插入。
let k1 = Symbol('k1'),
	k2 = Symbol('k2');

let o = {
	1: 1,
	first: 'first',
	[k1]: 'sym2',
	second: 'second',
	0: 0
};

o[k2] = 'sym2';
o[3] = 3;
o.third = 'third';
o[2] = 2;

console.log(Object.getOwnPropertyNames(o)); // ["0", "1", "2", "3", "first", "second", "third"] 

console.log(Object.getOwnPropertySymbols(o)); // [Symbol(k1), Symbol(k2)]

# 对象new操作符的原理

  1. 创建一个新的空对象
  2. 这个新对象内部的[[prototype]]特性被赋值为构造函数的prototype属性
  3. 构造函数内部的this被赋值为这个新对象(即this指向新对象)
  4. 执行构造函数内部的代码:给对象添加属性和方法
  5. 如果构造函数返回 一个对象 ,则返回该对象;否则返回刚创建的对象(如果return 1等数字/字符串/布尔值也是返回刚创建的对象)
function Foo() {
  this.user = 'Lucas';
  const o = {};
  return o;
}

const instance = new Foo();
console.log(instance.user); // undefined
function Foo() {
  this.user = 'Lucas';
  return 1;
}

const instance = new Foo();
console.log(instance.user); // Lucas
 var obj  = {};
 obj.__proto__ = Base.prototype;
 Base.call(obj);
var lily = new Person("Lily");
// 上述这段代码在运行时,JavaScript 引擎通过将Person的原型对象prototype赋值给实例对象lily的__proto__属性
// 实现了lily对Person的继承,即执行了以下代码:


// 实际上 JavaScript 引擎执行了以下代码
var lily = {};
lily.__proto__ = Person.prototype;
Person.call(lily, "Lily");

TIP

new对象: function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("Alice", 23);

new一个对象的四个过程:

1、创建一个空对象

var obj = new Object(); 

2、设置原型链,将obj的__proto__成员指向了Person函数对象的prototype成员对象

obj.__proto__ = Person.prototype; 

3、让Person中的this指向obj,并执行Person的函数体

var result = Person.call(obj);  

4、将初始化完毕的新对象地址,保存到等号左边的变量中

function A(name,age){
    console.log(this)
    this.name=name
    console.log(this)
    this.age=age 
    console.log(this)
	//return this //默认有这个操作,不管写不写
}

let b1=new A('111',19)
//{}
// {name: "111"}
//{name: "111", age: 19}

let b2=A('222',20)
//Window 
//Window 
//Window 

console.log(name)
//'222'

Object 构造函数创建一个对象包装器。

剩余/扩展属性

它将自己提供的对象的枚举属性复制到一个新的对象上。 使用比Object.assign()更短的语法,可以轻松克隆(不包括原型)或合并对象。

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };

var clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }

var mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }

请注意,Object.assign()会触发setter,而展开操作符则不会。

# 对象的方法

# Object.fromEntries 键值对数组转对象

console.warn(Object.fromEntries([
 ['foo', 'bar'],
 ['baz', 42]
]))

# Object.assign()

通过复制一个或多个对象来创建一个新的对象。

Object.assign(target, ...sources)

返回值 目标对象。

function A(){
	this.f1=10
	this.f2=20
}
A.prototype.f1=1000 
A.prototype.f3=3000

let childa=new A()

const object2 = Object.assign({c: 4, d: 5}, childa);
console.log(object2);//Object {c: 4, d: 5, f1: 10, f2: 20}
var obj1 = { name: "strick" },
 obj2 = Object.create(obj1); //name 是继承属性
obj2.age = 28; //age 是不可枚举的属性
Object.defineProperty(obj2, "age", {
 enumerable: false
});
obj2.school = "university"; //school 是可枚举的自有属性
Object.assign({}, obj2); //{school: "university"}
var obj1 = { [Symbol("name")]: "strick" },
 obj2 = Object.assign({}, obj1);
console.log(obj2); //{Symbol(name): "strick"}
var obj = Object.assign({}, 1, "a", true, undefined, null);
console.log(obj); //{0: "a"}

提示

  • 只能复制可枚举的自有属性(定义在对象中),无法复制继承属性(定义在对象原型中)和不可枚举的属性。Symbol 类型的属性也能被复制。
  • 如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。
  • Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。
  • 它分配属性,而不仅仅是复制或定义新的属性。针对深拷贝,需要使用其他方法,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
  • 当源对象的位置是基本类型的值时,它们会被包装成对象,再进行合并。但由于undefined 和 null 没有包装对象,并且数字和布尔值的包装对象又没有可枚举的属性(不能使用for循环之类的方法),因此只有字符串的包装对象才能不被忽略,最终以数组的形式复制到目标对象中,
  • 源对象的访问器属性会变成目标对象的数据属性。代码如下所示,obj 对象包含一个名为 name 的访问器属性,在把它与空对象合并后,目标对象会有一个名为 name 的数据属性,值就是访问器属性中 get()方法的返回值。
var obj = {
 get name() {
 return "strick";
 }
};
Object.assign({}, obj); //{name: "strick"}
const object1 = {
	a: 1,
	b: 2,
	c: 3
};

function A(){
	this.f1=10
	this.f2=20
}
A.prototype.f1=1000 
A.prototype.f3=3000

let childa=new A()
//e的这种写法并不会像Object.create的第二个参数那样,虽然声明不可枚举,但是无效,反而认为是个属性
const object3 = Object.assign(childa,{c: 4, d: 5,e:{value:99,enumerable:false}});
console.log(object3);
const object1 = {
	a: 1,
	b: 2,
	c: 3
};

function A(){
	this.f1=10
	this.f2=20
}
A.prototype.f1=1000 
A.prototype.f3=3000

let childa=new A()
//
let oc = Object.create({},{bb:{value:17},cc:{value:20,enumerable:true},dd:{value:20,enumerable:false}})

const object3 = Object.assign(childa,oc);
console.log(object3);
// {f1: 10, f2: 20, cc: 20}
dest = {
  set a(val) {
    console.log(`Invoked dest setter with param ${val}`);
  }
};
src = {
  get a() {
    console.log('Invoked src getter');
    return 'foo';
  }
};

Object.assign(dest, src);
// Invoked src getter
// Invoked dest setter with param foo

//调用src的获取方法,调用dest的设置方法并传入foo参数
// 因为这里的设置函数并不执行赋值操作,所以实际上并没有把值转移过来
// 所以不能在两个对象间转移获取函数和设置函数。
console.log(dest);  // { set a(val) {...} }
console.log(dest.a)//undefind

可以通过目标对象上的设置函数观察到覆盖的过程

dest = {
  set id(x) {
    console.log(x);
  }
};

Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' });
// first
// second
// third

# Object.create(proto[, propertiesObject])

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

  • propertiesObject:对应于 Object.defineProperties() 的第二个参数
let obj=Object.create({a:1},{b:{value:17},c:{value:20,enumerable:true}})
console.log(obj)
//{c: 20, b: 17,[[prototype]]:{
//	a:1
//}}

for(let i in obj){
	console.log(i)
}
//c 
//a
//这种方式默认生成的对象是不可遍历的,除非enumerable设为true。 

# new Object和Object.create区别

  • new Object:通过构造函数的对象, 对象添加的属性 obj 是在自身实例下
  • Object.create:Object.create(proto[, propertiesObject]) :创建的新对象继承一个对象。 添加的属性 proto 是在原型下,添加的属性 propertiesObject 才在自身实例下 第二个参数创建非空对象的属性描述符默认是为 false 的,不可写,不可枚举,不可配置
// 一个原型 对象
let prototype = {
	getName: function () {
		return this.first + " " + this.last;
	},
	say: function () {
		console.log("hello")
	}
}
// 基于原型创建x
let x = Object.create(prototype);
x.first = "A";
x.last = "B";
console.log(x.getName());//A B
x.say();//hello
console.log(x.__proto__ === prototype)//true

# Object.getOwnPropertySymbols()

Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。

Object.getOwnPropertySymbols(obj)

# Object.defineProperty()

方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性并返回这个对象。 Object.defineProperty(obj, prop, descriptor)

已单独提出专题.跳转

# Object.defineProperties()

直接在一个对象上定义新的属性或修改现有属性,并返回该对象。 给对象添加多个属性并分别指定它们的配置。

Object.defineProperties(obj, props)

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
});

# Object.getOwnPropertyDescriptor()

方法返回指定对象上一个自有属性对应的属性描述符。( 自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性

Object.getOwnPropertyDescriptor(obj, prop)

参数

obj 需要查找的目标对象

prop 目标对象内属性名称(String类型)

let obj1={k:1};
console.log(Object.getOwnPropertyDescriptor(obj1,'k'))
{value: 1, writable: true, enumerable: true, configurable: true}

# Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。

let s1 = Symbol('foo'),
    s2 = Symbol('bar');


let o = {
  [s1]: 'foo val',
  [s2]: 'bar val',
  baz: 'baz val',
  qux: 'qux val'
};


Object.defineProperty(o,'foo',{
    value: "fooooo",
    //是否为枚举属性
    enumerable: false
})

console.log(Object.getOwnPropertyDescriptors(o));
//{baz: {…}, qux: {…}, foo: {…}, Symbol(foo): {…}, Symbol(bar): {…}}

# Object.getOwnPropertyNames(obj)

参数

obj 一个对象,其自身的可枚举和不可枚举属性的名称被返回

返回值 在给定对象上找到的自身属性对应的字符串数组。

function F(){

	}
	F.prototype.k=900;
	let obj=new F();
	obj.s=1;
	Object.defineProperty(obj,'j',{
		value:9
	})
	console.log(Object.getOwnPropertyNames(obj))// ["s", "j"] 
	//只返回自身,不返回原型链上的名称

# Object.entries(obj)

提示

返回对象的键值对组成的数组,不会遍历原型上的 ,与for in不同

 function F(){}
	F.prototype.k=1;
	F.prototype.t=8;
	let a=new F();
	a.p=9;
	a.o=1000;
	for(let i in a){
	   console.log(a[i])
		
	}//9 1000 1 0
	// for (let i of a){
	// 	console.log(i)
	// } 
	//对象不经过手动添加不能迭代,不能用for of
	console.log(Object.entries(a))//[['p',9],['o',1000]] 

# Object.keys(obj)

返回key组成的对象

# Object.values(obj)

返回value组成的对象

# Object.is(a,b)

只要a,b一模一样即可 包括NaN和NaN都是正确的

# obj.hasOwnProperty(prop)

prop: 要检测的属性 字符串 名称或者 Symbol。

返回值: 用来判断某个对象是否含有指定的属性的 Boolean。

TIP

所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。

function A(){
	this.age=20
	this.eat=function(){
		console.log(1)
	}
}
A.prototype.full=function(){
	console.log(2)
}

let a=new A()
console.log(a.hasOwnProperty('eat'))//true
console.log(a.hasOwnProperty('full'))//false
console.log(a.age)//20

class B{
	// name="tr" /*webpack工程化中会报错,单独写的没报错*/
	like(){
		console.log(3)
		console.log(this.age)
	}
}
let b=new B()
b.bl=function(){
	console.log(4)
}
console.log(b.hasOwnProperty('like'))//false
b.bl()//4

let c=new B();
//c.bl() TypeError: c.bl is not a function

对比 Object.seal():用Object.seal()密封的对象可以改变它们现有的属性。使用Object.freeze() 冻结的对象中现有属性是不可变的。

# Object.freeze

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。 但是默认不能冻结深层次对象。

let obj = {
  prop: 42
};

Object.freeze(obj);

obj.prop = 33;
// Throws an error in strict mode

console.log(obj.prop);
// expected output: 42

//删除某个属性无效
delete obj.a
console.log(obj)//{prop: 42}

//可置空
obj = null
console.log(obj)//null
//浅冻结
obj1 = {
  internal: {}
};

Object.freeze(obj1);
obj1.internal.a = 'aValue';


console.log(obj1.internal.a )// 'aValue'
// 深冻结函数.
function deepFreeze(obj) {

  // 取回定义在obj上的属性名
  var propNames = Object.getOwnPropertyNames(obj);

  // 在冻结自身之前冻结属性
  propNames.forEach(function(name) {
    var prop = obj[name];

    // 如果prop是个对象,冻结它
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // 冻结自身(no-op if already frozen)
  return Object.freeze(obj);
}

obj2 = {
  internal: {}
};

deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined\

# Object.freeze 面试题

const user = { 
	name: 'Joe', 
	age: 25, 
	pet: { 
		type: 'dog', 
		name: 'Buttercup' 
	} 
}; 
Object.freeze(user); 
user.pet.name = 'Daffodil'; 
console.log(user.pet.name); 
//Daffodil

Object.freeze 将对对象执行浅层冻结,但不会保护深层属性不被突变。在此示例中,我们将无法更改user.age,但对 user.pet.name 进行更改不会有问题。如果我们认为需要保护某个对象以免其被“彻底破坏”,则可以递归应用 Object.freeze 或使用现有的“deep freeze”库。

# Object.seal

对象封存,防止新属性添加或者存在出现被移除,原先的对象还是可以修改的。

const person ={name:"aaa"}
Object.seal(person)
person.name="bbb"
person.age=30;
delete person.name
console.log(person)//{name: "bbb"}

# 对象的一些有关面试题

  1. 属性名皆为字符串,所以覆盖了
let a={}, //数组也可
b="0",
c=0;
a[b]="skt"
a[c]="IG"
console.log(a[b])//IG
let a1={};
a1[undefined]="IG"
a1['undefined']="skt"
console.log(a1[undefined])//skt 运行转成字符串比较,实际存储则不是
let a1={};
let s={k:1}
a1[s]=1000
console.log(a1)//{[object Object]: 1000}

# Object.entries与Array.entries对比

  1. 调用方式与所属对象

Object.entries()是 Object 的静态方法,需传入对象作为参数:

const obj = { a: 1, b: 2 };
const entries = Object.entries(obj); // [ ['a', 1], ['b', 2] ]

Array.prototype.entries()是数组实例的方法,需通过数组调用:

const arr = ['a', 'b'];
const iterator = arr.entries(); // 返回迭代器
  1. 返回类型 Object.entries() :返回一个二维数组,包含对象的可枚举属性键值对。
// 示例输出:
[ ['key1', value1], ['key2', value2], ... ]

Array.prototype.entries():返回一个迭代器对象,可通过 for...of 或 next() 遍历,生成 [index, value] 形式的数组。

// 示例迭代结果:
for (const [index, value] of arr.entries()) {
  console.log(index, value); // 0 'a', 1 'b'
}
  1. 处理的数据结构

Object.entries()处理对象的自身可枚举属性,包括普通对象、数组(视为键值对集合)等。

const arr = [1, , 3];
arr.foo = 'bar';
console.log(Object.entries(arr)); 
// 输出:[ ['0', 1], ['2', 3], ['foo', 'bar'] ]

Array.prototype.entries() 处理数组的所有索引(包括稀疏位置),生成 [index, value]。空位的 value 为 undefined。

const sparseArr = [1, , 3];
const iterator = sparseArr.entries();
console.log([...iterator]); 
// 输出:[ [0, 1], [1, undefined], [2, 3] ]
  1. 稀疏数组处理

Object.entries():忽略未赋值的稀疏索引,仅包含实际存在的属性。

Array.prototype.entries(): 遍历所有索引(从 0 到 length-1),未赋值的索引对应 undefined。

  1. 使用场景

Object.entries() 常用于对象转数组结构后的遍历或操作:

// 遍历对象属性
for (const [key, value] of Object.entries(obj)) { ... }

// 转为 Map
const map = new Map(Object.entries(obj));

Array.prototype.entries()适用于需要同时操作数组索引和值的场景:

// 获取索引和值
for (const [index, value] of arr.entries()) { ... }

# 总结

特性 Object.entries() Array.prototype.entries()
调用方式 Object.entries(obj) arr.entries()
返回类型 二维数组 迭代器对象
处理目标 对象的可枚举属性 数组的所有索引(包括稀疏位置)
稀疏数据 忽略未赋值的属性 包含空位,值为 undefined
典型场景 对象转数组、遍历属性、转为 Map 同时操作数组的索引和值

delete谨慎使用 (opens new window)

最后更新: 4/18/2025, 6:39:49 PM