为什么对象在 JavaScript 中不可迭代?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29886552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why are Objects not Iterable in JavaScript?
提问by boombox
Why are objects not iterable by default?
为什么默认情况下对象不可迭代?
I see questions all the time related to iterating objects, the common solution being to iterate over an object's properties and accessing the values within an object that way. This seems so common that it makes me wonder why objects themselves aren't iterable.
我总是看到与迭代对象相关的问题,常见的解决方案是迭代对象的属性并以这种方式访问对象内的值。这似乎很常见,以至于我想知道为什么对象本身不可迭代。
Statements like the ES6 for...ofwould be nice to use for objects by default. Because these features are only available for special "iterable objects" which don't include {}objects, we have to go through hoops to make this work for objects we want to use it for.
for...of默认情况下,像 ES6这样的语句很适合用于对象。因为这些功能仅适用于不包含{}对象的特殊“可迭代对象” ,所以我们必须通过一些障碍才能使其适用于我们想要使用它的对象。
The for...of statement creates a loop Iterating over iterable objects(including Array, Map, Set, arguments object and so on)...
for...of 语句创建一个循环迭代可迭代对象(包括 Array、Map、Set、arguments 对象等)...
For example using an ES6 generator function:
例如使用 ES6生成器函数:
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);
}
}
The above properly logs data in the order I expect it to when I run the code in Firefox (which supports ES6):
当我在 Firefox (支持ES6)中运行代码时,上面按照我期望的顺序正确记录数据:


By default, {}objects are not iterable, but why? Would the disadvantages outweigh the potential benefits of objects being iterable? What are the issues associated with this?
默认情况下,{}对象不可迭代,但为什么呢?缺点是否会超过对象可迭代的潜在好处?与此相关的问题是什么?
In addition, because {}objects are different from "Array-like" collections and "iterable objects" such as NodeList, HtmlCollection, and arguments, they can't be converted into Arrays.
此外,由于{}对象不同于“类数组”集合和“可迭代对象”,例如NodeList、HtmlCollection、 和arguments,因此它们无法转换为数组。
For example:
例如:
var argumentsArray = Array.prototype.slice.call(arguments);
var argumentsArray = Array.prototype.slice.call(arguments);
or be used with Array methods:
或与 Array 方法一起使用:
Array.prototype.forEach.call(nodeList, function (element) {}).
Array.prototype.forEach.call(nodeList, function (element) {}).
Besides the questions I have above, I would love to see a working example on how to make {}objects into iterables, especially from those who have mentioned the [Symbol.iterator].This should allow these new {}"iterable objects" to use statements like for...of. Also, I wonder if making objects iterable allow them to be converted into Arrays.
除了我上面的问题,我很想看到一个关于如何将{}对象变成可迭代的工作示例,尤其是那些提到[Symbol.iterator]. 这应该允许这些新的{}“可迭代对象”使用像for...of. 另外,我想知道使对象可迭代是否允许将它们转换为数组。
I tried the below code, but I get a TypeError: can't convert undefined to object.
我尝试了下面的代码,但我得到了一个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
回答by aroth
I'll give this a try. Note that I'm not affiliated with ECMA and have no visibility into their decision-making process, so I cannot definitively say whythey have or have not done anything. However, I'll state my assumptions and take my best shot.
我会试试这个。请注意,我不隶属于 ECMA,并且无法了解他们的决策过程,因此我无法明确说明他们为什么做了或没有做任何事情。但是,我会陈述我的假设并尽我所能。
1. Why add a for...ofconstruct in the first place?
1. 为什么for...of首先要添加一个结构?
JavaScript already includes a for...inconstruct that can be used to iterate the properties of an object. However, it's not really a forEach loop, as it enumerates all of the properties on an object and tends to only work predictably in simple cases.
JavaScript 已经包含一个for...in可用于迭代对象属性的构造。然而,它并不是真正的 forEach 循环,因为它枚举了一个对象的所有属性,并且往往只在简单的情况下可预测地工作。
It breaks down in more complex cases (including with arrays, where its use tends to be either discouraged or thoroughly obfuscatedby the safeguards needed to for use for...inwith an array correctly). You can work around that by using hasOwnProperty(among other things), but that's a bit clunky and inelegant.
它在更复杂的情况下会崩溃(包括数组,在这种情况下,它的使用往往不鼓励或被正确使用for...in数组所需的保护措施彻底混淆)。您可以通过使用(除其他外)来解决这个问题,但这有点笨拙和不雅。 hasOwnProperty
So therefore my assumption is that the for...ofconstruct is being added to address the deficiencies associated with the for...inconstruct, and provide greater utility and flexibility when iterating things. People tend to treat for...inas a forEachloop that can be generally applied to any collection and produce sane results in any possible context, but that's not what happens. The for...ofloop fixes that.
因此,我的假设是for...of添加该构造是为了解决与该for...in构造相关的缺陷,并在迭代事物时提供更大的实用性和灵活性。人们倾向于将循环视为通常可以应用于任何集合并在任何可能的上下文中产生合理结果for...in的forEach循环,但事实并非如此。该for...of循环修复了。
I also assume that it's important for existing ES5 code to run under ES6 and produce the same result as it did under ES5, so breaking changes cannot be made, for instance, to the behavior of the for...inconstruct.
我还假设现有的 ES5 代码在 ES6 下运行并产生与在 ES5 下相同的结果很重要,因此不能对for...in构造的行为进行重大更改。
2. How does for...ofwork?
2.如何for...of运作?
The reference documentationis useful for this part. Specifically, an object is considered iterableif it defines the Symbol.iteratorproperty.
的参考文档是对于这部分是有用的。具体来说,iterable如果一个对象定义了Symbol.iterator属性,那么它就会被考虑。
The property-definition should be a function that returns the items in the collection, one, by, one, and sets a flag indicating whether or not there are more items to fetch. Predefined implementations are provided for some object-types, and it's relatively clear that using for...ofsimply delegates to the iterator function.
属性定义应该是一个函数,它返回集合中的项目,一个,一个,一个,并设置一个标志,指示是否还有更多的项目要获取。为一些对象类型提供了预定义的实现,并且使用for...of简单的委托给迭代器函数是相对清晰的。
This approach is useful, as it makes it very straightforward to provide your own iterators. I might say the approach could have presented practical issues due to its reliance upon defining a property where previously there was none, except from what I can tell that's not the case as the new property is essentially ignored unless you deliberately go looking for it (i.e. it will not present in for...inloops as a key, etc.). So that's not the case.
这种方法很有用,因为它可以非常简单地提供您自己的迭代器。我可能会说这种方法可能会带来实际问题,因为它依赖于定义以前没有的属性,除非我可以说情况并非如此,因为除非您故意去寻找它,否则基本上忽略了新属性(即它不会for...in作为键出现在循环中,等等)。所以事实并非如此。
Practical non-issues aside, it may have been considered conceptually controversial to start all objects off with a new pre-defined property, or to implicitly say that "every object is a collection".
撇开实际的非问题不谈,以新的预定义属性开始所有对象,或隐含地说“每个对象都是一个集合”,这可能在概念上被认为是有争议的。
3. Why are objects not iterableusing for...ofby default?
3. 为什么对象默认不iterable使用for...of?
My guessis that this is a combination of:
我的猜测是,这是以下各项的组合:
- Making all objects
iterableby default may have been considered unacceptable because it adds a property where previously there was none, or because an object isn't (necessarily) a collection. As Felix notes, "what does it mean to iterate over a function or a regular expression object"? - Simple objects can already be iterated using
for...in, and it's not clear what a built-in iterator implementation could have done differently/better than the existingfor...inbehavior. So even if #1 is wrong and adding the property was acceptable, it may not have been seen as useful. - Users who want to make their objects
iterablecan easily do so, by defining theSymbol.iteratorproperty. - The ES6 spec also provides a Maptype, which is
iterableby default and has some other small advantages over using a plain object as aMap.
iterable默认设置所有对象可能被认为是不可接受的,因为它添加了一个以前没有的属性,或者因为一个对象(必然)不是一个集合。正如 Felix 所指出的,“迭代一个函数或一个正则表达式对象是什么意思”?- 简单的对象已经可以使用 进行迭代
for...in,目前尚不清楚内置迭代器实现可以比现有for...in行为做得不同/更好。因此,即使 #1 是错误的并且添加属性是可以接受的,它也可能不被视为有用。 - 想要创建对象的
iterable用户可以通过定义Symbol.iterator属性轻松实现。 - 所述ES6规范还提供了一个地图类型,它是
iterable通过缺省,并且使用普通的对象作为具有其他的一些小的优点Map。
There's even an example provided for #3 in the reference documentation:
参考文档中甚至为 #3 提供了一个示例:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
for (var value of myIterable) {
console.log(value);
}
Given that objects can easily be made iterable, that they can already be iterated using for...in, and that there's likely not clear agreement on what a default object iterator should do (if what it does is meant to be somehow different from what for...indoes), it seems reasonable enough that objects were not made iterableby default.
鉴于对象可以很容易地创建iterable,它们已经可以使用 进行迭代for...in,并且对于默认对象迭代器应该做什么(如果它所做的意味着与所做的有所不同for...in)可能没有明确的共识,这似乎是合理的足以使iterable默认情况下不制作对象。
Note that your example code can be rewritten using for...in:
请注意,您的示例代码可以使用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"
}
}
...or you can also make your object iterablein the way you want by doing something like the following (or you can make allobjects iterableby assigning to Object.prototype[Symbol.iterator]instead):
...或者您也可以iterable通过执行以下操作以您想要的方式创建对象(或者您可以通过分配来创建所有对象):iterableObject.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
};
};
You can play with that here (in Chrome, at lease): http://jsfiddle.net/rncr3ppz/5/
你可以在这里玩(至少在 Chrome 中):http: //jsfiddle.net/rncr3ppz/5/
Edit
编辑
And in response to your updated question, yes, it is possible to convert an iterableto an array, using the spread operatorin ES6.
针对您更新的问题,是的,可以iterable使用ES6 中的展开运算符将 an 转换为数组。
However, this doesn't seem to be working in Chrome yet, or at least I cannot get it to work in my jsFiddle. In theory it should be as simple as:
但是,这似乎还不能在 Chrome 中工作,或者至少我无法在我的 jsFiddle 中使用它。理论上它应该很简单:
var array = [...myIterable];
回答by aroth
Objects don't implement the iteration protocols in Javascript for very good reasons. There are two levels at which object properties can be iterated over in JavaScript:
Object出于很好的理由,不要在 Javascript 中实现迭代协议。在 JavaScript 中有两个级别可以迭代对象属性:
- the program level
- the data level
- 程序级别
- 数据层
Program Level Iteration
程序级迭代
When you iterate over an object at the program level you examine a portion of the structure of your program. It is a reflective operation. Let's illustrate this statement with an array type, which is usually iterated over at the data level:
当您在程序级别迭代对象时,您会检查程序结构的一部分。这是一个反射操作。让我们用一个数组类型来说明这个语句,它通常在数据级别迭代:
const xs = [1,2,3];
xs.f = function f() {};
for (let i in xs) console.log(xs[i]); // logs `f` as well
We just examined the program level of xs. Since arrays store data sequences, we are regularly interested in the data level only. for..inevidently makes no sense in connection with arrays and other "data-oriented" structures in most cases. That is the reason why ES2015 has introduced for..ofand the iterable protocol.
我们刚刚检查了xs. 由于数组存储数据序列,我们通常只对数据级别感兴趣。for..in在大多数情况下,显然与数组和其他“面向数据”结构没有任何意义。这就是 ES2015 引入for..of可迭代协议的原因。
Data Level Iteration
数据级迭代
Does that mean that we can simply distinguish the data from the program level by distinguishing functions from primitive types? No, because functions can also be data in Javascript:
这是否意味着我们可以通过区分函数和原始类型来简单地区分数据和程序级别?不,因为函数也可以是 Javascript 中的数据:
Array.prototype.sortfor instance expects a function to perform a certain sort algorithm- Thunks like
() => 1 + 2are just functional wrappers for lazily evaluated values
Array.prototype.sort例如期望一个函数执行某种排序算法- Thunks
() => 1 + 2只是懒惰评估值的功能包装器
Besides primitive values can represent the program level as well:
除了原始值也可以表示程序级别:
[].lengthfor instance is aNumberbut represents the length of an array and thus belongs to the program domain
[].length例如 is aNumberbut 代表一个数组的长度,因此属于程序域
That means that we can't distinguish the program and data level by merely checking types.
这意味着我们不能仅仅通过检查类型来区分程序和数据级别。
It is important to understand that the implementation of the iteration protocols for plain old Javascript objects would rely on the data level. But as we've just seen, a reliable distinction between data and program level iteration is not possible.
重要的是要了解普通旧 Javascript 对象的迭代协议的实现将依赖于数据级别。但正如我们刚刚看到的,数据和程序级迭代之间的可靠区分是不可能的。
With Arrays this distinction is trivial: Every element with an integer-like key is a data element. Objects have an equivalent feature: The enumerabledescriptor. But is it really advisable to rely on this? I believe it is not! The meaning of the enumerabledescriptor is too blurry.
对于Arrays,这种区别是微不足道的:每个具有类似整数的键的元素都是一个数据元素。Objects 有一个等效的功能:enumerable描述符。但是,依靠这个真的可取吗?我相信不是!enumerable描述符的含义太模糊了。
Conclusion
结论
There is no meaningful way to implement the iteration protocols for objects, because not every object is a collection.
没有有意义的方法来实现对象的迭代协议,因为不是每个对象都是一个集合。
If object properties were iterable by default, program and data level were mixed-up. Since every composite type in Javascript is based on plain objects this would apply for Arrayand Mapas well.
如果默认情况下对象属性是可迭代的,那么程序和数据级别就会混淆。由于在Javascript中每个复合类型是基于原生的对象,这将适用于Array和Map为好。
for..in, Object.keys, Reflect.ownKeysetc. can be used for both reflection and data iteration, a clear distinction is regularly not possible. If you're not careful, you end up quickly with meta programming and weird dependencies. The Mapabstract data type effectively ends the conflating of program and data level. I believe Mapis the most significant achievement in ES2015, even if Promises are much more exciting.
for..in, Object.keys,Reflect.ownKeys等可用于反射和数据迭代,通常不可能有明确的区别。如果你不小心,你很快就会遇到元编程和奇怪的依赖关系。的Map抽象数据类型有效地结束程序和数据电平的混为一谈。我相信Map是 ES2015 中最重要的成就,即使Promises 更令人兴奋。
回答by aroth
I guess the question should be "why is there no built-inobject iteration?
我想问题应该是“为什么没有内置对象迭代?
Adding iterability to objects themselves could conceivably have unintended consequences, and no, there is no way to guarantee order, but writing an iterator is as simple as
为对象本身添加可迭代性可能会产生意想不到的后果,不,没有办法保证顺序,但编写迭代器就像这样简单
function* iterate_object(o) {
var keys = Object.keys(o);
for (var i=0; i<keys.length; i++) {
yield [keys[i], o[keys[i]]];
}
}
Then
然后
for (var [key, val] of iterate_object({a: 1, b: 2})) {
console.log(key, val);
}
a 1
b 2
回答by Hyman Slocum
You can easily make all objects iterable globally:
您可以轻松地使所有对象全局可迭代:
Object.defineProperty(Object.prototype, Symbol.iterator, {
enumerable: false,
value: function * (){
for(let key in this){
if(this.hasOwnProperty(key)){
yield [key, this[key]];
}
}
}
});
回答by Chad Scira
This is the latest approach (which works in chrome canary)
这是最新的方法(适用于 chrome canary)
var files = {
'/root': {type: 'directory'},
'/root/example.txt': {type: 'file'}
};
for (let [key, {type}] of Object.entries(files)) {
console.log(type);
}
Yes entriesis now a method thats part of Object :)
Yesentries现在是 Object 的一部分的方法:)
edit
编辑
After looking more into it, it seems you could do the following
在深入研究之后,您似乎可以执行以下操作
Object.prototype[Symbol.iterator] = function * () {
for (const [key, value] of Object.entries(this)) {
yield {key, value}; // or [key, value]
}
};
so you can now do this
所以你现在可以这样做
for (const {key, value:{type}} of files) {
console.log(key, type);
}
edit2
编辑2
Back to your original example, if you wanted to use the above prototype method it would like like this
回到你原来的例子,如果你想使用上面的原型方法,它会像这样
for (const {key, value:item1} of example) {
console.log(key);
console.log(item1);
for (const {key, value:item2} of item1) {
console.log(key);
console.log(item2);
}
}
回答by Romko
I was also bothered with this question.
我也被这个问题困扰。
Then I came up with an idea of using Object.entries({...}), it returns an Arraywhich is an Iterable.
然后我想出了一个使用的想法Object.entries({...}),它返回一个Array是一个Iterable.
Also, Dr. Axel Rauschmayer posted an excellent answer on this. See Why plain objects are NOT iterable
此外,Axel Rauschmayer 博士对此发表了出色的回答。请参阅为什么普通对象不可迭代
回答by Manngo
Technically, this is not an answer to the question why?but I have adapted Hyman Slocum's answer above in light of BT's comments to something which can be used to make an Object iterable.
从技术上讲,这不是为什么的问题的答案?但我已经根据 BT 的评论改编了上面 Hyman Slocum 的回答,使其成为可用于使对象可迭代的东西。
var iterableProperties={
enumerable: false,
value: function * () {
for(let key in this) if(this.hasOwnProperty(key)) yield this[key];
}
};
var fruit={
'a': 'apple',
'b': 'banana',
'c': 'cherry'
};
Object.defineProperty(fruit,Symbol.iterator,iterableProperties);
for(let v of fruit) console.log(v);
Not quite as convenient as it should have been, but it's workable, especially if you have multiple objects:
不像它应该的那样方便,但它是可行的,特别是如果你有多个对象:
var instruments={
'a': 'accordion',
'b': 'banjo',
'c': 'cor anglais'
};
Object.defineProperty(instruments,Symbol.iterator,iterableProperties);
for(let v of instruments) console.log(v);
And, because every one is entitled to an opinion, I can't see why Objects are not already iterable either. If you can polyfill them as above, or use for … inthen I can't see a simple argument.
而且,因为每个人都有权发表意见,我不明白为什么对象还不是可迭代的。如果你可以像上面那样对它们进行 polyfill,或者使用,for … in那么我看不到一个简单的论点。
One possible suggestion is that what is iterable is a typeof object, so it is possible that iterable has been limited to a subset of objects just in case some other objects explode in the attempt.
一个可能的建议是 iterable 是一种对象类型,因此 iterable 可能被限制为对象的子集,以防其他一些对象在尝试中爆炸。

