使属性不可枚举有哪些好处?

57

枚举性是属性的三个属性之一:可写性、枚举性和配置性。我的问题是:

  • 在JavaScript中使属性不可枚举的好处是什么?我知道我们通过使它们不可枚举来隐藏属性,但属性隐藏的好处是什么?
  • 我们能访问不可枚举的属性吗?如果可以,那么使它们不可枚举的好处是什么?
  • 所有对象的预定义属性是否都设置为不可枚举?例如Array的poppush属性是否为不可枚举?

1
我认为主要的好处是使for in循环更加安全 - 在迭代对象时,该属性不会显示出来。也许我忘记了枚举的作用... - Ian
4个回答

45

我认为主要好处在于能够控制枚举对象属性时显示的内容,例如for inObject.keys()

MDN通过Object.defineProperty进行了很好的解释:https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty

因此,通常情况下,当人们想要向Object添加一个方法(例如某些旧浏览器不支持的polyfill)时,他们会修改.prototype。但是,这会使属性可枚举,并且会混淆在循环/键集合中返回的内容(而不使用.hasOwnProperty...并非每个人都使用它)。

所以,与其像这样做:

Object.prototype.myMethod = function () {
    alert("Ahh");
};

您可以使用 Object.defineProperty 明确指定它不可枚举:

Object.defineProperty(Object.prototype, 'myMethod', {
    value: function () {
        alert("Ahh");
    },
    enumerable: false
});
例如,当您使用for (var key in obj)时,“myMethod”不会被枚举,并且您无需担心使用.hasOwnProperty。主要问题是某些浏览器当然不支持它:http://kangax.github.com/es5-compat-table/,并且并非所有库/代码都使用它,因此您不能始终依赖外部库/代码以正确和始终正确地使用它。
您可以随时访问不可枚举的属性,只是在枚举对象的属性时不会显示它-这是主要问题。
我确实相信对象的所有“预定义”属性都是不可枚举的。我的意思只是指本机属性,不一定是继承或已创建的属性。因此,在您的示例中,poppush将不会被枚举,但是如果将Array.prototype.indexOf作为旧浏览器上的填充使用,则会被枚举...当然,可以通过像上面的示例那样使用Object.defineProperty来避免。另一个例子是不会枚举的length属性。
以下是一个一般示例:http://jsfiddle.net/aHJ3g/ 使用和定义Object.keys很重要:“返回给定对象的自己可枚举的属性数组,顺序与由for-in循环提供的顺序相同(不同之处在于for-in 循环也会枚举原型链中的属性)。”-来自MDN- https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys

14

我认为另一个重要的好处是,它可以防止对象的私有属性污染公共命名空间。

比如说你创建并发布了一个名为 Cosmos 的强大库。用户启动 Node 解释器并通过调用构造函数创建了它的新实例:

var Cosmos = require('Cosmos');
var cosmos = new Cosmos('my empire');

现在用户只需键入cosmos并按Enter键即可查看它支持的公共API。你想让用户看到这两个中的哪一个?

{ name: 'my empire',
  grow: [Function: grow],
  addStar: [Function: addStar],
  beautify: [Function: beautify],
  implode: [Function: implode],
  destroy: [Function: destroy] }

或者

{ _age: 25000,
  _size: 35000,
  _destroyed: false,
  name: 'my empire',
  _numStars: 200,
  _init: [Function: _init],
  grow: [Function: grow],
  _grow: [Function: _grow],
  addStar: [Function: addStar],
  _checkStatus: [Function: _checkStatus],
  beautify: [Function: beautify],
  implode: [Function: implode],
  destroy: [Function: destroy] }

9
不可枚举属性仍然是公共的,并且仍会污染命名空间。如果您想要私有属性,您需要使用闭包。 - Matthew
5
我赞同回答和评论。评论的批评对我来说似乎是有效的,因此回答者使用“public”和“private”可能不是最佳选择。然而,我认为回答的观点仍然很有用(至少对我来说是这样),因为它显示了可枚举性如何帮助库用户更轻松地看到库作者旨在让用户轻松使用的内容(例如,请使用grow而不是_grow,即使两者都从技术上来说是公共的)。 - Andrew Willems

0

非可枚举属性没有真正的实际用途

这是一个非常好的问题,我本来也想问自己。经过一番调查,我的结论是:你绝对不需要这个功能。

背景

对于那些不知道什么是非可枚举属性的人,请看下面的链接:

实际上你可以枚举它们

这很奇怪,但是枚举所谓的“非可枚举”属性实际上非常简单:

// start with some enumerable properties
const foo = {
  a: 1,
  b: "yes",
  c: function () {}
}

// then add a couple of non-enumerable ones
Object.defineProperty(foo, "d", { value: "hiding here", isEnumerable: false });
Object.defineProperty(foo, "e", { value: 42, isEnumerable: false });

const enumerableProperties = Object.keys(foo).join(", ");
console.info("Enumerables: " + enumerableProperties);
// Enumerables: a, b, c

const ownPropertyNames = Object.getOwnPropertyNames(foo).join(", ");
console.info("Enumerating also the non-enumerables: " + ownPropertyNames);
// Enumerating also the non-enumerables: a, b, c, d, e

当他们说你不能枚举它们时,他们特指Object.keys()for..in循环以及它们只返回可枚举属性。而对于getOwnPropertyNames()来说并非如此。

不要使用它

好的,既然我们在同一个页面上:在我看来,这似乎是一种晦涩的语言特性,只会使我的代码更难阅读和理解。我简直想不出真正合理的用途。

现有的答案都谈到了两个非常具体的情况:

  1. 当你需要处理第三方对象的原型并添加一些polyfill方法,而又不想在特定情况下破坏现有代码时,这是非常有用的。如果你的情况是如此,那么你需要支持非常旧的代码(即不符合当今最佳实践的遗留代码),我为你感到遗憾(尽管我知道今天仍然有很多系统在维护中属于这种情况,不幸的是);
  2. 当你开发一个公共库并希望保持公共接口的整洁时,这是非常有用的。这是非常具体的情况,对吧?而且我也不愿意为此在我的库代码中添加多个defineProperty。此外,这个答案正在迅速过时,因为我们现在有了私有字段。最后,这个答案还声称它可以保持公共命名空间的整洁,但事实并非如此;命名空间被对象本身污染,而不是它的属性。
所以,除非你不幸地处于需要维护旧代码的位置,否则就别使用这个功能了。如果你发现自己需要在枚举对象时隐藏某些属性,那么你肯定是对其进行了错误的建模。只需将这些属性放在一个单独的对象中即可,它们并不应该与其他属性一起存在。这样可以使你的代码更清晰、更易理解,而这正是你在编写代码时应该努力实现的最重要的事情。
这里有一篇更详细的文章支持这个观点,证明非可枚举属性在当今已经没有什么重要作用了。

0
  • 通过将属性设置为不可枚举,您仍然可以访问它。但是,当您在对象上应用for in循环时,不可枚举的属性不会被迭代。
  • 见第一点
  • 继承的属性是可枚举的(只要它们被标记为可枚举)

    var x = {a:1, b:2} // a和b默认情况下是可枚举属性
    x.propertyIsEnumerable("toString") // 返回false,因为它没有标记为可枚举
    var y = Object.create(x);
    y.c = 3;
    for(p in y) console.log(p); // 这个循环将打印c、a和b,但不包括toString
    

3
我认为第三点是错误的。继承的属性与其他属性一样可枚举......例如:var x = {a: 1}; var y = Object.create(x); y.b = 2; for (name in y) {console.log(name, y[name]);},将输出 b 2a 1 - Scott Sauyet
1
我修正了我的回答。请原谅我的错误! - daniatic

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