为什么对象不能在JavaScript中进行迭代?

为什么对象默认不能迭代?

我总是看到与迭代对象有关的问题,常见的解决scheme是迭代对象的属性,并以这种方式访问​​对象内的值。 这似乎很常见,这使我想知道为什么对象本身不可迭代。

像ES6 for...of这样的语句默认使用对象。 因为这些特性只适用于不包含{}对象的特殊“可迭代对象”,所以我们必须经过这些循环才能使这个对象成为我们想要使用的对象。

for …语句创build一个循环遍历可迭代对象 (包括Array,Map,Set,arguments object等)…

例如使用ES6 生成器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 )的代码:

hacky为...输出

默认情况下, {}对象不可迭代,但是为什么? 这些缺点是否超过了可迭代对象的潜在好处? 与此相关的问题是什么?

另外,由于{}对象与“类似数组”的集合和“可迭代对象”(如NodeListHtmlCollectionarguments ,因此不能将其转换为数组。

例如:

var argumentsArray = Array.prototype.slice.call(arguments);

或与数组方法一起使用:

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,对决策过程也没有了解,所以我不能确定地说出为什么他们有或没有做任何事情。 然而,我会陈述我的假设,并采取我最好的一击。

为什么在一开始for...of添加一个for...of构造呢?

JavaScript已经包含一个for...in构造,可用于迭代对象的属性。 然而,它并不是一个真正的forEach循环 ,因为它枚举了一个对象上的所有属性,并且在简单的情况下往往只能预测地工作。

它在更复杂的情况下被破坏(包括数组,其使用倾向于被阻止,或被 for...in的数据正确使用的安全措施彻底混淆 )。 你可以通过使用hasOwnProperty (等等)来解决这个问题,但是这有点笨重和不雅。

因此,我的假设是,为了解决与for...in相关的缺陷,我们增加了for...in构造,并在迭代时提供更大的实用性和灵活性。 人们倾向于将for...in作为forEach循环来处理for...in通常可以应用于任何集合,并在任何可能的上下文中产生理智的结果,但这不是发生的事情。 for...of循环修复了这个问题。

我还假定现有的ES5代码在ES6下运行并产生与在ES5下相同的结果是非常重要的,因此不能违反更改,例如, for...in构造的行为。

2.如何工作?

参考文档对于这部分是有用的。 具体而言,如果对象定义了Symbol.iterator属性,则该对象被认为是iterable

属性定义应该是一个函数,返回集合中的项目,一个,一个,并设置一个标志,指示是否有更多的项目要取。 为某些对象types提供了预定义的实现,并且使用for...of简单地委托给迭代器函数是相对清楚的。

这种方法是有用的,因为它提供你自己的迭代器非常简单。 我可能会说这种方法可能会提出一些实际问题,因为它依赖于定义一个以前没有的财产,除非我能说的不是这样,因为除非你故意去找它,否则新的财产本质上是被忽略的。它不会出现在for...in作为一个关键循环等)。 所以情况并非如此。

除了实际的非问题之外,可能会被认为在概念上有争议的是用新的预定义属性开始所有的对象,或者隐含地说“每个对象都是一个集合”。

3.为什么默认情况下对象不能被iterable使用?

我的猜测是,这是一个组合:

  1. 使默认情况下可iterable的所有对象可能被认为是不可接受的,因为它添加了一个以前没有的属性,或者因为对象不是(必然)是一个集合。 正如Felix指出的:“迭代函数或正则expression式对象意味着什么?
  2. 简单的对象已经可以用for...in来迭代,而且不清楚内置的迭代器实现可以做什么不同于现有的for...in行为。 所以,即使#1是错的,增加财产是可以接受的,也许没有被认为是有用的
  3. 想要使其对象iterable用户可以通过定义Symbol.iterator属性轻松完成此Symbol.iterator
  4. ES6规范还提供了一个Maptypes,默认情况下 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 (或者你可以通过赋值给Object.prototype[Symbol.iterator]来使所有的对象都可以iterable ):

 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 }; }; 

你可以在这里玩(在铬,在租赁): http : //jsfiddle.net/rncr3ppz/5/

编辑

为了回应您的更新问题,是的,可以使用ES6中的扩展运算符将iterable转换为数组。

但是,这似乎还没有在Chrome中工作,或者至less我不能让它在我的jsFiddle工作。 理论上它应该如此简单:

 var array = [...myIterable]; 

我想这个问题应该是“为什么没有内置的对象迭代?

给对象本身添加迭代本身可能会产生意想不到的后果,不,没有办法保证顺序,但是编写一个迭代器就像

 function* iterate_object(o) { var keys = Object.keys(o); for (var i=0; i<keys.length; i++) { yield [keys[i], o[keys[i]]]; } } 

然后

 for (var [key, val] of iterate_object({a: 1, b: 2})) { console.log(key, val); } a 1 b 2 

由于很好的原因, Object并没有在Javascript中实现迭代协议。 有两个级别可以在JavaScript中迭代对象属性:

  • 程序级别
  • 数据级别

程序级迭代

当您在程序级别迭代对象时,将检查程序的一部分结构。 这是一个反思性的操作。 让我们用一个数组types来说明这个语句,通常在数据层次上进行迭代:

 const xs = [1,2,3]; xs.f = function f() {}; for (let i in xs) console.log(xs[i]); // logs `f` as well 

这是最新的方法(在铬金丝雀工作)

 var files = { '/root': {type: 'directory'}, '/root/example.txt': {type: 'file'} }; for (let [key, {type}] of Object.entries(files)) { console.log(type); } 

entries现在是一个方法那是对象的一部分:)

编辑

仔细观察之后,似乎可以做到以下几点

 Object.prototype[Symbol.iterator] = function * () { for (const [key, value] of Object.entries(this)) { yield {key, value}; // or [key, value] } }; 

所以你现在可以做到这一点

 for (const {key, value:{type}} of files) { console.log(key, type); } 

EDIT2

回到你原来的例子,如果你想使用上面的原型方法,它会喜欢这样的

 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); } }