天天看点

Object属性的可枚举性和遍历方法

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      

【注意】:

  1. 如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。
  2. 由于​

    ​undefined​

    ​​和​

    ​null​

    ​​无法转为对象,所以如果第一个参数是​

    ​undefined​

    ​​或​

    ​null​

    ​,就会报错。

1.4 Object.getPrototypeOf()

该方法与​

​Object.setPrototypeOf​

​方法配套,用于读取一个对象的原型对象。

Object.getPrototypeOf(obj);      

【注意】:

  1. 如果参数不是对象,会被自动转为对象。
  2. 如果参数是​

    ​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 遍历语法的比较

对于数组来说:

  1. for循环:最原始的写法;
  2. forEach:无法中途跳出​

    ​forEach​

    ​​循环,​

    ​break​

    ​​命令或​

    ​return​

    ​命令都不能奏效;
  3. for…in…:遍历可以获得数组的键名,有以下缺点:数组的键是​

    ​number​

    ​,而遍历出来的键是字符串;​

    ​for...in​

    ​​循环不仅遍历数字键名,还会遍历手动添加其他键,甚至包括原型链上的键;在某些情况下,​

    ​for...in​

    ​​循环会以任意顺序遍历键名。总之,​

    ​for...in​

    ​循环主要是为了遍历对象而设计的,不适用于遍历数组。
  4. ​for...of​

    ​​:遍历数组。有着同​

    ​for..in​

    ​​样简单语法,但是没有​

    ​for...in​

    ​​那些缺点,不同于 ​

    ​forEach ​

    ​​方法,它可以与 ​

    ​break​

    ​​ 、 ​

    ​continue ​

    ​​和 ​

    ​return​

    ​ 配合使用。提供了遍历所有数据结构的统一操作接口。

对于对象来说:

遍历方式\属性特点 是否可以遍历描述符为​

​enumberable:false​

​的属性
是否可以遍历自身的属性 是否可以遍历原型上的属性

​for...in... ​

​Object.getOwnPropertyNames​

​Object.keys​

例如:

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']      

继续阅读