为什么默认情况下无法迭代对象?
我经常看到与迭代对象有关的问题,常见的解决方案是迭代对象的属性并以这种方式访问对象中的值。这似乎很常见,使我想知道为什么对象本身不可迭代。
默认情况下,ES6 for...of
之类的语句很适合用于对象。因为这些功能仅适用于不包含{}
对象的特殊“可迭代对象”,所以我们必须仔细研究一下才能使要使用它的对象起作用。
例如,使用ES6 generator function:
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(example)) {
console.log(key);
console.log(value);
for (let [key, value] of entries(value)) {
console.log(key);
console.log(value);
}
}
上面的代码按照我在Firefox(支持ES6)中运行代码时期望的顺序正确记录了数据:
默认情况下,
{}
对象不是可迭代的,但是为什么呢?缺点会超过对象可迭代的潜在好处吗?与此相关的问题是什么? 另外,由于
{}
对象与 NodeList
, HtmlCollection
和 arguments
之类的“类似数组的”集合和“可迭代对象”不同,因此无法将其转换为数组。例如:
var argumentsArray = Array.prototype.slice.call(arguments);
或与Array方法一起使用:
Array.prototype.forEach.call(nodeList, function (element) {})
。除了上面的问题之外,我还想看到一个有效的示例,说明如何将
{}
对象转换为可迭代对象,尤其是那些提到了[Symbol.iterator]
的对象。 这应该允许这些新的{}
“可迭代对象”使用类似for...of
的语句。此外,我想知道是否使对象可迭代允许它们转换为数组。我尝试了以下代码,但得到了
TypeError: can't convert undefined to object
。var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
// I want to be able to use "for...of" for the "example" object.
// I also want to be able to convert the "example" object into an Array.
example[Symbol.iterator] = function* (obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
};
for (let [key, value] of example) { console.log(value); } // error
console.log([...example]); // error
最佳答案
我会尝试的。请注意,我不隶属于ECMA,并且对他们的决策过程不了解,所以我不能确切地说出为什么他们没有做任何事情。但是,我会陈述自己的假设并尽我最大的努力。
1.为什么首先要添加for...of
构造?
JavaScript已经包含for...in
构造,可用于迭代对象的属性。但是,它是not really a forEach loop,因为它枚举了对象上的所有属性,并且仅在可预见的情况下才能正常工作。
它在更复杂的情况下会崩溃(包括在数组中,由于正确地将for...in
与数组一起使用所需的防护措施,其使用往往是discouraged or thoroughly obfuscated)。您可以使用hasOwnProperty
(除其他方法外)解决此问题,但这有点笨拙且不雅致。
因此,因此我的假设是添加了for...of
构造以解决与for...in
构造相关的缺陷,并在迭代时提供更大的实用性和灵活性。人们倾向于将for...in
视为forEach
循环,该循环通常可以应用于任何集合,并在任何可能的上下文中产生理智的结果,但是事实并非如此。 for...of
循环可解决此问题。
我还假设对于现有的ES5代码来说,在ES6下运行并产生与ES5下相同的结果非常重要,因此无法对for...in
构造的行为进行重大更改。
2. for...of
如何工作?
reference documentation对于此部分很有用。具体来说,如果对象定义了iterable
属性,则将其视为Symbol.iterator
。
属性定义应该是一个函数,该函数一个接一个地返回集合中的项目,并设置一个标志,该标志指示是否还有更多要提取的项目。为some object-types提供了预定义的实现,并且很明显,使用for...of
可以简单地委派给迭代器函数。
这种方法很有用,因为它使提供自己的迭代器变得非常简单。我可能会说,由于依赖于定义以前没有属性的属性,因此该方法可能会带来实际问题,但我可以说不是这样,因为除非您有意去寻找它,否则新属性实际上会被忽略(即它不会作为关键字出现在for...in
循环中)。事实并非如此。
除了实用的非问题,从概念上讲,以新的预定义属性开始所有对象,或隐式地说“每个对象都是一个集合”在概念上是有争议的。
3.为什么默认情况下对象不使用iterable
进行for...of
?
我的猜测是这是以下各项的组合:
iterable
可能被认为是 Not Acceptable ,因为它在以前没有属性的地方添加了一个属性,或者因为(不一定)对象不是一个集合。正如Felix所说,“遍历函数或正则表达式对象意味着什么?” for...in
迭代简单的对象,尚不清楚内置迭代器实现与现有for...in
行为相比有何不同/更好。因此,即使#1错误并且添加了该属性是可以接受的,它也可能未被认为有用。 iterable
属性,想要使自己的对象成为Symbol.iterator
的用户可以轻松实现。 iterable
,与使用普通对象作为Map
相比,还有其他一些小优点。 引用文档中甚至为#3提供了一个示例:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
for (var value of myIterable) {
console.log(value);
}
鉴于可以轻松地将对象设置为
iterable
,可以使用for...in
对其进行迭代,并且对于默认对象迭代器应该做什么(可能与for...in
有所不同)尚无明确的共识,似乎足够合理,默认情况下不将对象设置为iterable
。请注意,您的示例代码可以使用
for...in
重写:for (let levelOneKey in object) {
console.log(levelOneKey); // "example"
console.log(object[levelOneKey]); // {"random":"nest","another":"thing"}
var levelTwoObj = object[levelOneKey];
for (let levelTwoKey in levelTwoObj ) {
console.log(levelTwoKey); // "random"
console.log(levelTwoObj[levelTwoKey]); // "nest"
}
}
...或者您也可以通过执行以下操作来以所需的方式使对象
iterable
成为可能(或者,也可以通过分配给iterable
来使所有对象成为Object.prototype[Symbol.iterator]
):obj = {
a: '1',
b: { something: 'else' },
c: 4,
d: { nested: { nestedAgain: true }}
};
obj[Symbol.iterator] = function() {
var keys = [];
var ref = this;
for (var key in this) {
//note: can do hasOwnProperty() here, etc.
keys.push(key);
}
return {
next: function() {
if (this._keys && this._obj && this._index < this._keys.length) {
var key = this._keys[this._index];
this._index++;
return { key: key, value: this._obj[key], done: false };
} else {
return { done: true };
}
},
_index: 0,
_keys: keys,
_obj: ref
};
};
您可以在此处(在Chrome浏览器中租借)使用它:http://jsfiddle.net/rncr3ppz/5/
编辑
回答您的更新问题,是的,可以使用ES6中的spread operator将
iterable
转换为数组。但是,这似乎在Chrome中尚不可用,或者至少我无法在jsFiddle中使用它。从理论上讲,它应该很简单:
var array = [...myIterable];
关于javascript - 为什么在JavaScript中对象不可迭代?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59363620/