1 可枚举性
1.1 Object.getOwnPropertyDescriptor()
对象的每一个属性都有一个描述对象(
Descriptor
),用来控制该属性的行为。使用
Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。例如:
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
=> {
value: 123,
writable: true,
enumerable: true,
configurable: true
}
描述对象的
enumerable
属性,称为“可枚举性”,如果该属性为
false
,就表示某些操作会忽略当前属性。目前,有四个操作会忽略
enumerable
为
false
的属性。
-
循环:只遍历对象自身的和继承的可枚举的属性。for...in
-
:返回对象自身的所有可枚举的属性的键名。[推荐使用Object.keys()
遍历]Object.keys()
-
:只串行化对象自身的可枚举的属性。JSON.stringify()
-
: 忽略Object.assign()
为enumerable
的属性,只拷贝对象自身的可枚举的属性。false
这四个操作之中,前三个是
ES5
就有的,最后一个
Object.assign()
是
ES6
新增的。其中,只有
for...in
会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。实际上,引入“可枚举”(
enumerable
)这个概念的最初目的,就是让某些属性可以规避掉
for...in
操作,不然所有内部属性和方法都会被遍历到。比如,对象原型的
toString
方法,以及数组的
length
属性,就通过“可枚举性”,从而避免被
for...in
遍历到。
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false
上面代码中,
toString
和
length
属性的
enumerable
都是
false
,因此
for...in
不会遍历到这两个继承自原型的属性。另外,
ES6
规定,所有
Class
的原型的方法都是不可枚举的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false
总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用
for...in
循环,而用
Object.keys()
代替。
1.2 Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors
方法,返回指定对象所有自身属性(非继承属性)的描述对象。
const obj = { foo: 123, get bar() { return 'abc' } };
Object.getOwnPropertyDescriptors(obj)
=> { foo:
{ value: 123,
writable: true,
enumerable: true,
configurable: true },
bar:
{ get: [Function: bar],
set: undefined,
enumerable: true,
configurable: true }
}
Object.getOwnPropertyDescriptors(obj)
方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。
1.3 Object.setPrototypeOf()
用于设置一个对象的原型对象。
// 格式
Object.setPrototypeOf(object, prototype)
// 用法
const o = Object.setPrototypeOf({}, null);
// 该方法等同于下面的函数。
function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
例如:
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40
【注意】:
- 如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。
- 由于
和undefined
无法转为对象,所以如果第一个参数是null
或undefined
,就会报错。null
1.4 Object.getPrototypeOf()
该方法与
Object.setPrototypeOf
方法配套,用于读取一个对象的原型对象。
Object.getPrototypeOf(obj);
【注意】:
- 如果参数不是对象,会被自动转为对象。
- 如果参数是
或undefined
,它们无法转为对象,所以会报错。null
1.5 super关键字
我们知道,
this
关键字总是指向函数所在的当前对象,
ES6
又新增了另一个类似的关键字
super
,指向当前对象的原型对象。
const proto = { foo: 'hello' };
const obj = {
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
// 上面代码中,对象obj的find方法之中,通过super.foo引用了原型对象proto的foo属性。
注意,
super
关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。
JavaScript
引擎内部,
super.foo
等同于
Object.getPrototypeOf(this).foo
(属性)或
Object.getPrototypeOf(this).foo.call(this)
(方法)。
例如:
const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
// 上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world。
1.6 Object.keys(),Object.values(),Object.entries()
Object.keys
方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(
enumerable
)属性的键名。
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj) // ["foo", "baz"]
ES2017
引入了跟
Object.keys
配套的
Object.values
和
Object.entries
,作为遍历一个对象的补充手段,供
for...of
循环使用。
Object.values
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(
enumerable
)属性的键值。
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj) // ["b", "c", "a"]
上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是
b、c、a
。
Object.entries
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(
enumerable
)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]
除了返回值不一样,该方法的行为与
Object.values
基本一致。
2 遍历
2.1 遍历方法
(1)
for...in
for...in
循环遍历对象自身的和继承的可枚举属性(不含
Symbol
属性)
(2)
Object.keys(obj)
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含
Symbol
属性)的键名
(3)
Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含
Symbol
属性,但是包括不可枚举属性)的键名。
(4)
Reflect.ownKeys(obj)
Reflect.ownKeys
返回一个数组,包含对象自身的所有键名,不管键名是
Symbol
或字符串,也不管是否可枚举。
2.2 遍历语法的比较
对于数组来说:
- for循环:最原始的写法;
- forEach:无法中途跳出
循环,forEach
命令或break
命令都不能奏效;return
- for…in…:遍历可以获得数组的键名,有以下缺点:数组的键是
,而遍历出来的键是字符串;number
循环不仅遍历数字键名,还会遍历手动添加其他键,甚至包括原型链上的键;在某些情况下,for...in
循环会以任意顺序遍历键名。总之,for...in
循环主要是为了遍历对象而设计的,不适用于遍历数组。for...in
-
:遍历数组。有着同for...of
样简单语法,但是没有for..in
那些缺点,不同于 for...in
方法,它可以与 forEach
、 break
和 continue
配合使用。提供了遍历所有数据结构的统一操作接口。return
对于对象来说:
遍历方式\属性特点 | 是否可以遍历描述符为 的属性 | 是否可以遍历自身的属性 | 是否可以遍历原型上的属性 |
| √ | √ | |
| √ | √ | |
| √ |
例如:
class Person { }
// 定义原型属性 prototypeAttr
Person.prototype.prototypeAttr = 'prototypeAttr';
const jack = new Person('jack');
// 定义实例自身属性 selfAttr
jack.selfAttr = 'selfAttr';
// 定义实例修饰符为 enumerable: false 的属性
Object.defineProperty(jack, 'enumberableFalseAttr', {
value: 'enumberableFalseAttr',
enumerable: false,
writable: true,
configurable: true
使用
for...in...
遍历:
for (let key in jack) {
console.log(key);
}
=> selfAttr
=> prototypeAttr
Object.getOwnPropertyNames(jack)
=>['selfAttr','enumberableFalseAttr']
Object.keys(jack);
=> ['selfAttr']