最近在学习对象遍历的方法时总是能看到的两个词,一个是“原型”,一个是“枚举属性”。一开始感觉自己大概明白“枚举属性”的意思,但是叫我解释却又说不出个所以然来,所以上网查了一些资料更系统的了解一下什么是“枚举属性”。

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

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

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

Object,Array,Number等

       这是一个例子:

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

for ... in 访问到 

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

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

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,我是原型属性
}

原型链中的属性,不管是否枚举都会返回false。
    但是 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”没有输出。

  

javascript枚举类 js中枚举_原型链

 b.  Object.keys()

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

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

    

javascript枚举类 js中枚举_原型链_02

c. JSON.stringify

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

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

    

javascript枚举类 js中枚举_javascript枚举类_03

 

       从上面这些操作中大概可以明白了,可枚举性决定了这个属性能否被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])//a,可以被枚举
}
//18
//xiao

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

 

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

       有时候不知道对象的可枚举性,该怎么判断呢。propertylsEnumerable()方法可以解决这个问题
      

person.propertyIsEnumerable('sex');//false
person.propertyIsEnumerable('age');//true

     propertyIsEnumerable() 语法:

  1. 语法:obj.propertyIsEnumerable(prop)
  2. 描述:每个对象都有一个propertyIsEnumerable方法。此方法可以确定对象中指定的属性是否可枚举,返回一个布尔值。但该方法对通过原型链继承的属性无效(原型链继承的属性是否可枚举不能用该方法来判断)
  3. 案例:
    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和基本包装类型里的属性是不可枚举的,
 如Object, Array, Number等

          其实,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()代替。