可枚举是什么意思?

174

当我阅读MDN的for..in页面时,它说,“for..in用于遍历对象的可枚举属性。”

然后我去了可枚举性和属性所有权页面,它说:“可枚举属性是那些可以被for..in循环迭代的属性。”

词典将“可枚举”定义为可计数的,但我无法真正想象这意味着什么。我能否得到一个关于可枚举的例子?


你理解 for-in 的作用吗? - user2437417
从我得到的答案中,可以看出for..in允许使用对象的所有可枚举属性与for语句一起使用。 - Patrick
6
最简单的意思是:无论是否通过 for in 循环生成属性(对于那些不想知道具体细节或者根本不关心的人;) - Tomasz Racia
8个回答

183
可枚举属性是指可以在 for..in 循环(或类似遍历属性的方法,如 Object.keys())中包含并访问的属性。
如果一个属性没有被标记为可枚举,循环将忽略该属性在对象中的存在。
var obj = { key: 'val' };

console.log('toString' in obj); // true
console.log(typeof obj.toString); // "function"

for (var key in obj)
    console.log(key); // "key"

属性的可枚举性是由其自身的[[Enumerable]]属性确定的。您可以将其视为属性描述符的一部分:

var descriptor = Object.getOwnPropertyDescriptor({ bar: 1 }, 'bar');

console.log(descriptor.enumerable); // true
console.log(descriptor.value);      // 1

console.log(descriptor);
// { value: 1, writable: true, enumerable: true, configurable: true }

一个 for..in 循环然后遍历对象的属性名称。
var foo = { bar: 1, baz: 2};

for (var prop in foo)
    console.log(prop); // outputs 'bar' and 'baz'

但是,仅在属性的[[Enumerable]]属性为true时才评估其语句 - 在这种情况下是console.log(prop);

之所以设置这个条件,是因为对象有很多更多的属性, 特别是来自继承

console.log(Object.getOwnPropertyNames(Object.prototype));
// ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", /* etc. */]

这些属性仍然存在于对象上

console.log('constructor' in foo); // true
console.log('toString' in foo);    // true
// etc.

但是,它们被for..in循环跳过,因为它们不可枚举。

var descriptor = Object.getOwnPropertyDescriptor(Object.prototype, 'constructor');

console.log(descriptor.enumerable); // false

因此,构造函数属性是不可枚举的,因为它不会在for..in循环中显示。而bar或baz等属性的名称是可枚举的,因为您可以从对象中打印出每个属性名。 - Patrick
7
这并不完全正确:“但是,它们被for..in循环跳过(或不计算)是因为它们不可枚举”。实际上,Object.prototype上的属性被忽略是因为它们在原型链上更高层次,而不是因为它们不可枚举。getOwnPropertyNames()keys()都不会显示不属于对象本身的属性。顺带一提,许多内置属性确实不可枚举,因此在使用keys()时不会显示,但如果您想遍历原型链,则需要编写额外的代码。 - Magnus
字面上,"enumerable" 意味着可计数。 - Christian Matthew
2
@Magnus 他们正在使用 for..in,而不是 keys()for..in确实会遍历原型链,因此非枚举是唯一阻止内置属性进入的事情。 - D0SBoots

55
如果您通过myObj = {foo:'bar'}或类似方法创建对象,则所有属性都是可枚举的。因此,更容易回答的问题是什么不是可枚举的?某些对象具有一些非可枚举属性,例如,如果调用Object.getOwnPropertyNames([])(它返回[]上的所有属性(包括可枚举和不可枚举的)的数组),它将返回['length'],其中包括数组的非可枚举属性“length”。
您可以通过调用Object.defineProperty来创建自己的非可枚举属性:
var person = { age: 18 };
Object.defineProperty(person, 'name', { value: 'Joshua', enumerable: false });

person.name; // 'Joshua'
for (prop in person) {
  console.log(prop);
}; // 'age'

这个例子很大程度上借鉴了JavaScript中的不可枚举属性,但展示了一个对象的枚举。属性可以是可写的,也可以是不可写、可配置的或不可配置的、可枚举的或不可枚举的。John Resig在ECMAScript 5 对象和属性的范围内讨论了这个问题。

此外,还有一个关于为什么要使属性成为非可枚举的的Stack Overflow问题。


嗯,既有赞同也有反对。当然,如果这个答案有什么值得反对的地方,请在这里添加评论。 - carpeliam
1
另一个答案写道: var o = {}; o['foo'] = 0; // 可枚举,正常 Object.defineProperty(o, 'bar', {value: 1}); // 不可枚举,奇怪为什么要显式添加 enumerable: false? - Patrick
6
@Patrick,我在这里部分地包含了它,以便从示例中清楚地看出这是一个选项,但根据你的受众,明确表达可能是一个明智的决定,这样任何阅读你代码的人都能清楚地理解。 - carpeliam
@carpeliam 谢谢您提供的简短明了的解释,直到我阅读了您的解释,我才明白了被接受答案的含义 :D - sandeepKumar

43

它比应该被可视化的东西要无聊得多。

每个属性都有一个名为"enumerable"的属性。当它被设置为false时,for..in方法将跳过该属性,并假装它不存在。

许多对象上的属性都将"enumerable"设置为false,例如"valueOf"和"hasOwnProperty",因为假定你不希望JavaScript引擎遍历这些属性。

您可以使用Object.defineProperty方法创建自己的非枚举属性:

  var car = {
    make: 'Honda',
    model: 'Civic',
    year: '2008',
    condition: 'bad',
    mileage: 36000
  };

  Object.defineProperty(car, 'mySecretAboutTheCar', {
    value: 'cat pee in back seat',
    enumerable: false
  });

现在,汽车存在一个秘密,并且被隐藏了起来。当然,他们仍然可以直接访问该属性并得到答案:

console.log(car.mySecretAboutTheCar); // prints 'cat pee in back seat'

但是,他们必须首先知道该属性的存在,因为如果他们试图通过for..inObject.keys来访问它,它将完全保密:

console.log(Object.keys(car)); //prints ['make', 'model', 'year', 'condition', 'mileage']

他们应该把它称为 "forInAble"。


5
为那句话点赞——我认为如果它真的被称为forInAble,没有人会问任何问题。这带出了我的问题——为什么您(或为什么您必须)将属性设置为不可枚举? 数组的长度属性经常被用作非可枚举属性的示例,但是为什么不能将其设置为可枚举(不是JavaScript的限制,我是说,为什么在JavaScript中它被设置为不可枚举)? - Chris
为了澄清我的问题,为什么决定不选择以下选项:制造商:Honda 型号:Civic 秘密:猫尿 长度:12 - Chris
嗯...我可能已经找到了自己问题的答案。从谷歌搜索“什么是枚举数据类型?”中得知,枚举数据具有有限的值集。枚举数据类型由您允许该类型的值或枚举值组成。对于基于整数的枚举类型,每个枚举值都包含一个名称和一个基础数字值。所以,如果我理解正确,由于可能存在无限数量的长度,您将不得不为每个长度分配一个无限数量的名称。这正确吗? - Chris
关于你的第一个问题,给对象设置一个不可枚举属性有点巧妙,因为你唯一能找到该对象上的内容的方式就是查看代码,而随着代码库的发展,它可能会被埋藏起来。所以,一般来说,我们不会想要使用它。至于 '.length',它用于获取字符串中的字符数或数组中的元素数,如果我们使用 'for... in' 循环,通常我们不希望包括它。不能回答为什么 Js 的众神会这样设计它,他们有超出我们理解范围的原因(咳咳)和观点。 - Gabriel Kunkel
我不认为我能回答你关于枚举数据类型的第二个问题,尽管如果我有时间的话,这似乎是我可以咀嚼很长时间的东西。 :-) - Gabriel Kunkel
很棒的答案,将简单的事情简化是一种技巧 :) - johnwick0831

10

我将简要定义可枚举的(ENUMERABLE)

可枚举的(Enumerable): 指定属性是否可以在for/in循环中返回。

var obj = {};
Object.defineProperties(obj, {
    set1: {enumerable: true},
    set2: {enumerable: false},
});
Object.keys(obj); // ["set1"]
Object.getOwnPropertyNames(obj); // ["set1", "set2"]

10
如果你很难想象什么是“可枚举”,为什么不反过来思考一下,“不可枚举”是什么意思呢?
我这样想,一个“不可枚举”的属性是存在的,但是部分被隐藏了;也就是说“不可枚举”的属性比较奇怪。那么,你现在可以将“可枚举”看作是剩下的部分——我们发现“对象”以来遇到的更自然的属性。请考虑:
var o = {};
o['foo'] =  0;                               // enumerable, normal
Object.defineProperty(o, 'bar', {value: 1}); // nonenumerable, weird

现在,对于一个for..in循环,请将其想象为伪代码。

for property in o:
    if not property enumerable continue // skip non-enumerable, "bar"
    else do /* whatever */              // act upon enumerable, "foo"

在你输入的JavaScript循环体部分,替换为/* whatever */


3
对象继承的内置方法是不可枚举的,但是您的代码添加到对象中的属性是可枚举的,除非明确说明。

1

想象一下枚举数据类型,它只是一个由不同数字对应的对象构成的结构。将某物声明为可枚举就是声明它与特定数字相对应,使其能够在表示对象可计数组件的字典中占据一个位置。简单来说,使对象可枚举就等同于告诉编译器:“嘿,这个属性有用,我希望在检查此对象的数据时看到它。”


0

方法不可枚举;或者更确切地说,内置方法不可枚举。但在查找JavaScript中的可枚举的含义后,发现它只是一个属性属性。在ECMA3中,所有创建的对象都是可枚举的,在ECMA5中,您现在可以定义它...就是这样...:D 哈哈,我花了一点时间才找到答案。但我相信David Flanagan的书中有提到这个问题...所以我想它的意思是“隐藏”,或者在for in循环中不显示方法,因此是“隐藏”的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接