在我们查看选项之前,先快速了解一下有关 JavaScript 属性的四个关键事项:
- 对象可以拥有自己的属性,以及从原型对象继承的属性。
- 属性可以是可枚举的 or 不可枚举.
- 属性的名称可以是strings,或(从 ES2015/ES6 开始)名称
Symbol
s.
- 属性不能有数字名称,比如 1。有时我们的行为就像它们一样,就像我们处理数组时一样,但它们却不然。标准阵列根本不是真正的数组 http://blog.niftysnippets.org/2011/01/myth-of-arrays.html(根据规范;JavaScript 实现可以在可能的情况下进行优化),数组中的条目是名称为字符串的对象属性。所以
a = ['x', 'y', 'z']
定义一个具有三个属性的数组,其名称为"0"
, "1"
, and "2"
。当我们这样做时a[0]
访问第一个,数字0
被转换为字符串。 (理论上;同样,JavaScript 实现是允许优化的。)
所有这些属性都可以被发现和枚举(甚至是不可枚举的属性)。
您有多种选择:
- A
for-in
loop (spec http://www.ecma-international.org/ecma-262/6.0/index.html#sec-for-in-and-for-of-statements | MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in),有或没有hasOwnProperty http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.prototype.hasownproperty在循环内部进行保护以区分“自己的”属性和继承的属性。 (不包括以Symbol
s.) 循环names的属性。
-
Object.keys
(spec http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.keys | MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) (ES5+),它返回对象自身的可枚举属性的名称数组。 (不包括以Symbol
s.)
-
Object.getOwnPropertyNames
(spec http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.getownpropertynames | MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames) (ES5+),它返回对象自身属性名称的数组,无论它们是否可枚举。 (不包括以Symbol
s.)
-
Reflect.enumerate
(spec http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.enumerate | MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/enumerate) (ES2015+), which returns an iterator for the names of the enumerable properties of an object, including ones it inherits. (Does not include properties named with Symbol
s.) It was removed in ES2016.
-
Object.getOwnPropertySymbols
(spec http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.getownpropertysymbols | MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols) (ES2015+),它返回一个对象自身属性的名称数组Symbol
s,无论它们是否可枚举。 (省略那些用字符串命名的。)
-
Reflect.ownKeys
(spec http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.ownkeys | MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys) (ES2015+),它返回对象自身属性名称的数组,无论它们如何命名 (Symbol
或字符串),以及它们是否可枚举。
正如你所看到的,大多数操作只包含名称为字符串的属性,只有Object.getOwnPropertySymbols
and Reflect.ownKeys
给我们命名为Symbol
s.
键的顺序未定义(甚至在 ES2015 中也未定义):for-in
or Object.keys
。在 ES2015 及以上版本中,is为其他四个定义,由[[OwnPropertyKeys]] http://www.ecma-international.org/ecma-262/6.0/index.html#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys和(如适用)[[Enumerate]] http://www.ecma-international.org/ecma-262/6.0/index.html#sec-ordinary-object-internal-methods-and-internal-slots-enumerate运营。 (由于 ES2015 [截至撰写本文时] 仍相对为人所知,因此可能并非所有 JavaScript 引擎都正确实现了该顺序。)
让我们看一下例子。首先,一些设置:
// Create an object with one "own" property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}
// Create an object that uses the above as its prototype
var obj = Object.create(proto);
// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;
// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, "four", {
value: 4,
configurable: true,
writable: true
});
(Object.create
ES5 中添加了这一功能,但仅采用一个参数的版本 [如上所述] 可以轻松地为过时的 JavaScript 引擎(例如 IE8 中的引擎)进行填充/填充。Object.defineProperty
ES5 中也添加了该内容,并且无法正确填充/填充。)
由于大多数操作仅涉及由字符串命名的属性,因此我们忽略Symbol
现在是这样。
一旦上面的代码运行,我们就会在内存中保存它(*
名称旁边的 表示它是不可枚举的属性):
+−−−−−−−−−−−−−−−−−+
Object.prototype−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−>| toString* |−−>(...a function...)
| | valueOf* |−−>(...a function...)
| | hasOwnProperty* |−−>(...a function...)
| | ... |
| +−−−−−−−−−−−−−−−−−+
|
+−−−−−−−−−−−−−−−+ |
proto−−−−−−−−−−−−−−−−−−−−+−−>| [[Prototype]] |−−+
| | one: 1 |
| +−−−−−−−−−−−−−−−+
|
+−−−−−−−−−−−−−−−+ |
obj−−>| [[Prototype]] |−−+
| two: 2 |
| three: 3 |
| four*: 4 |
+−−−−−−−−−−−−−−−+
有了这个设置,让我们看看我们的选项:
for-in
for-in
循环遍历名称为字符串的对象的所有属性(包括从其原型继承的属性)的名称(忽略名称为字符串的所有属性)Symbol
s).
for (var name in obj) {
// name is the name of each property, so:
console.log(name + " = " + obj[name]);
}
// Create an object with one "own" property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}
// Create an object that uses the above as its prototype
var obj = Object.create(proto);
// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;
// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, "four", {
value: 4,
configurable: true,
writable: true
});
for (var name in obj) {
// name is the name of each property, so:
console.log(name + " = " + obj[name]);
}
至此,我们看到
two = 2
three = 3
one = 1
...或类似的。即使在 ES2015 中,您看到的属性的顺序也没有定义。
for-in
with a hasOwnProperty
guard
如果我们想要循环,但忽略继承的属性,我们可以添加一个hasOwnProperty
check:
for (var name in obj) {
if (obj.hasOwnProperty(name)) {
console.log(name + " = " + obj[name]);
}
}
// Create an object with one "own" property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}
// Create an object that uses the above as its prototype
var obj = Object.create(proto);
// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;
// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, "four", {
value: 4,
configurable: true,
writable: true
});
for (var name in obj) {
if (obj.hasOwnProperty(name)) {
console.log(name + " = " + obj[name]);
}
}
至此,我们看到
two = 2
three = 3
我们没有看到one
因为它是遗传的。
Since hasOwnProperty
是对象上的一个方法,理论上它可以被一个没有返回我们期望的版本覆盖:
obj.hasOwnProperty = function() {
return true;
};
...这会欺骗我们上面的循环。这通常不是问题,但如果您想防范它,请使用Object.prototype
反而:
var hasOwn = Object.prototype.hasOwnProperty;
for (var name in obj) {
if (hasOwn.call(obj, name)) {
console.log(name + " = " + obj[name]);
}
}
当然,有人可以分配给Object.prototype.hasOwnProperty
财产也是如此,但如果这样做,相当多的东西可能会损坏。
Object.keys
(ES5+,易于填充/填充)
Object.keys
给我们一个对象名称的数组自己的,可枚举的用字符串命名的属性。因此它不包括继承的属性、标记为不可枚举的属性或以Symbol
s.
var propNames = Object.keys(obj);
然后,我们可以通过多种方式循环遍历该数组中的条目,例如forEach
:
propNames.forEach(function(name) {
console.log(name + " = " + obj[name]);
});
// Create an object with one "own" property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}
// Create an object that uses the above as its prototype
var obj = Object.create(proto);
// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;
// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, "four", {
value: 4,
configurable: true,
writable: true
});
var propNames = Object.keys(obj);
propNames.forEach(function(name) {
console.log(name + " = " + obj[name]);
});
通过我们的示例设置,我们可以得到:
two = 2
three = 3
规范中没有定义数组中名称的顺序,即使在 ES2015 中也没有定义。
Object.getOwnPropertyNames
(ES5+)
Object.getOwnPropertyNames
返回以字符串命名的对象自身属性名称的数组,无论是否可枚举。它省略了命名为的属性Symbol
s.
var propNames = Object.getOwnPropertyNames(obj);
propNames.forEach(function(name) {
console.log(name + " = " + obj[name]);
});
// Create an object with one "own" property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}
// Create an object that uses the above as its prototype
var obj = Object.create(proto);
// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;
// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, "four", {
value: 4,
configurable: true,
writable: true
});
var propNames = Object.getOwnPropertyNames(obj);
propNames.forEach(function(name) {
console.log(name + " = " + obj[name]);
});
通过我们的示例设置,我们可以得到:
two = 2
three = 3
four = 4
数组中名称的顺序由[[OwnPropertyKeys]] http://www.ecma-international.org/ecma-262/6.0/index.html#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys规范中的操作,其顺序为:
- 符合条件的属性名称整数索引 http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object-type,按数字顺序。
- 其他属性的名称,按照属性的创建顺序排列。
所以我们得到two
, three
, four
因为这些都不符合规范对整数索引的定义,而这就是我们添加属性的顺序。如果我们以不同的顺序添加它们,或者包含符合索引的顺序,我们会得到不同的结果:
var obj2 = {};
obj2.four = 4;
obj2[0] = "zero";
obj2.two = 2;
obj2.three = 3;
Object.getOwnPropertyNames(obj2).forEach(function(name) {
console.log(name + " = " + obj2[name]);
});
var obj2 = {};
obj2.four = 4;
obj2[0] = "zero";
obj2.two = 2;
obj2.three = 3;
Object.getOwnPropertyNames(obj2).forEach(function(name) {
console.log(name + " = " + obj2[name]);
});
给我们:
0 = zero
four = 4
two = 2
three = 3
0
首先是因为虽然它是一个字符串,但它符合整数索引的标准。然后我们得到four
因为它是先创建的,然后two
, then three
.
Reflect.enumerate
(ES2015) Removed in ES2016
Reflect.enumerate
在 ES2016 中被删除。
Reflect.enumerate
uses the new iterator feature of ES2015. It returns an iterator that will iterate over the names of the string-named enumerable properties of the object, including inherited ones, skipping ones named with Symbol
or that aren't enumerable. First it visits the "own" properties (in the order defined by [[OwnPropertyKeys]]
, and then inherited properties (unless they've been hidden by "own" properties).
我们可以使用新的for-of
循环遍历它们:
for (let name of Reflect.enumerate(obj)) {
console.log(name + " = " + obj[name]);
}
通过我们的设置,我们可以得到:
two = 2
three = 3
one = 1
one
位于最后,因为它是继承的,并且“自己的”属性排在第一位。
注意:同样,由于 ES2015 在撰写本文时相对较新,因此某些 JavaScript 引擎可能无法实现Reflect
还没有对象。