Reflect.ownKeys()与Object.keys()区别 以及 JS中的可枚举属性与不可枚举属性

    xiaoxiao2025-04-02  5

    代码test1:

    var obj = {} Object.defineProperty(obj, 'method1', { value: function () { alert("Non enumerable property"); }, enumerable: false }) console.log(Object.keys(obj)) // [] console.log(Reflect.ownKeys(obj)) // ["method1"]

    代码test2:

    const obj = { id1: 42, id2: 13 }; console.log(Object.keys(obj)) // ['id1', 'id2'] console.log(Reflect.ownKeys(obj)) // ['id1', 'id2']

    总结: Object.keys()主要用于遍历对象自有的可枚举属性,不包括继承自原型的属性和不可枚举的属性。 Reflect.ownKeys()返回所有自有属性key,不管是否可枚举,但不包括继承自原型的属性

    官方对Refelct.ownKeys()的描述如下:

    The Reflect.ownKeys method returns an array of the target object's own property keys. Its return value is equivalent to Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)).


    JS中遍历对象属性的三种方法:

    for in

    主要用于遍历对象的可枚举属性,包括自有属性、继承自原型的属性

    Object.keys

    返回一个数组,元素均为对象自有的可枚举属性

    Object.getOwnPropertyNames

    用于返回对象的自有属性,包括可枚举和不可枚举的

    注意:


    什么是枚举?枚举是指对象中的属性是否可以遍历出来,再简单点说就是属性是否可以被列举出来。

    一、怎么判断属性是否可枚举

    在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for…in查找遍历到。

    JS中的基本包装类型的原型属性是不可枚举的,基本包装类型看下图:

    下面是一个例子:

    var num = new Number(); for(var pro in num) { console.log("num." + pro + " = " + num[pro]); } // 此处没有输出

    它的输出结果是空的,因为Number的原型属性是不可枚举的,所以不能被 for ... in 访问到。

     

    二、枚举属性的作用

    枚举属性主要会影响几个方法

    ES5中:     for...in                         //只遍历对象自身的和继承的可枚举的属性     Object.keys()             //返回对象自身的所有可枚举的属性的键名     JSON.stringify           //JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。  ES6中:     Object.assign()          //会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

    可以看出来这些都是可以遍历对象的方法,而这四个操作中只有for...in中会返回继承的属性

    先看一个例子,创建一个"xsy"对象:

    function Person(){ this.name = "XSY" }; Person.prototype = { constructor: Person, job:"student", }; var xsy = new Person(); Object.defineProperty(xsy, "sex",{ value:"female", enumerable:false });

    这里用defineProperty方法定义了一个叫"sex"的不可枚举属性

    然后可以开始验证了:

    a.  for...in

    for(var pro in xsy){ console.log("xsy." + pro+ " = " + xsy[pro]); }

    输出的结果如下,可以发现 对象中声明的属性,原型链上绑定的属性成功输出了,而不可枚举属性“sex”没有输出。

    b.  Object.keys()

    console.log(Object.keys(xsy));

    从输出结果可以发现,这里只输出了对象声明的可枚举属性,但是没有输出原型链中的可枚举属性

    c. JSON.stringify

    console.log(JSON.stringify(xsy));

    这里的输出也和上面一样,结果中只有对象中的可枚举属性没有原型链中的。

     

    从上面这些操作中大概可以明白了,可枚举性决定了这个属性能否被for…in查找遍历到。所以可枚举与否都是开发者自己定义的,可以通过Object.defineProperty()方法。

     

    三、设置可枚举属性

    其实在上面的例子中已经使用到了设置enumerable的方法:Object.defineProperty()

    var person = { name:'xiao', age: '18', sex: 'boy' } Object.defineProperty(person,'age',{ enumerable:true //可以被枚举 }); Object.defineProperty(person,'sex',{ enumerable:false //不可以被枚举 }) for(var k in person){ console.log(person[k]) } //xiao //18

    从上面可以看出:

    Object.defineProperty(obj, prop, descriptor)方法有三个参数 第一个:目标对象 第二个:目标属性,字符串 第三个:对目标属性的行为,放在对象里enumerable为true时表示可枚举,enumerable为false表示不可枚举;开发者自己定义的对象person中的所有属性默认都是可枚举的;

     

    四、如何判断是否可枚举 -- propertyIsEnumerable

    每个对象都有propertyIsEnumerable()方法,这个方法可以判断出指定的属性是否可枚举。

    用法: obj.propertyIsEnumerable("属性名");

    这个属性必须属于实例的,并且不属于原型。这个属性必须是可枚举的。如果对象没有指定的属性,该方法返回false function Person(){ this.name = "我是实例属性" this.age = 19; } var p = new Person(); console.log(p.propertyIsEnumerable("name")); //true Person.prototype.prop = "我是原型属性" //添加一个原型属性 console.log(p.propertyIsEnumerable("prop")); //false prop是继承自原型上的属性,所以返回的是false for(var k in p){ console.log(k+","+p[k]); //name,我是实例属性 age,19 prop,我是原型属性 }

     

    案例: 1)用户自定义对象和引擎内置对象的区别

    Math.propertyIsEnumerable('random'); // 返回 false Object.propertyIsEnumerable('constructor'); // 返回 false var num = new Number(); for(var pro in num) { console.log("num." + pro + " = " + num[pro]); } //输出空

    这说明了开发者自定义的属性在一般情况下时可以枚举的,但是内置的对象Math和基本包装类型里的属性是不可枚举的。

    其实,propertyIsEnumerable方法只对对象自身的属性(对象自身添加的、构造函数实例化的)有效,对原型上的、继承来的属性都无效。

     

    五、总结

    实际上,引入enumerable的最初目的,就是让某些属性可以规避掉for...in操作。比如,对象原型的toString方法,以及数组的length属性,就通过这种手段,不会被for...in遍历到。

    具体如下:

    Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable // false Object.getOwnPropertyDescriptor([], 'length').enumerable // false

    另外, ES6 规定,所有 Class 的原型的方法都是不可枚举的。

    Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable // false

    总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in循环,而用Object.keys()代替。

    最新回复(0)