为什么对象不能在JavaScript中迭代? [英] Why are Objects not Iterable in JavaScript?

查看:127
本文介绍了为什么对象不能在JavaScript中迭代?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么默认情况下对象不可迭代?



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



如ES6 , HtmlCollection 参数 ,它们不能转换成数组。



例如:



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



或与Array方法一起使用:



Array.prototype.forEach.call(nodeList,function(element){})



除了上面提到的问题,我想要看到一个关于如何使 {} 对象进入的工作示例迭代,特别是那些谁提到了 [Symbol.iterator] 这应该允许这些新的 {} 可迭代对象使用 for 之类的语句。此外,我想知道如果使对象可迭代允许将它们转换为数组。



我尝试了以下代码,但是我得到一个 TypeError:can不要将未定义转换为对象

  var example = {a:{e:'one' ,f:'two'},b:{g:'three'},c:{h:'four',i:'five'}}; 

//我想要为example对象使用for ...。
//我也希望能够将example对象转换成一个数组。
example [Symbol.iterator] = function *(obj){
for(let key of Object.keys(obj)){
yield [key,obj [key]];
}
};

for(let [key,value]的例子){console.log(value); } // error
console.log([... example]); //错误


解决方案

我会尝试一下。请注意,我不隶属于ECMA,并且不了解他们的决策过程,所以我不能明确地说,为什么他们有或没有做任何事情。但是,我会说出我的假设,并拍下最好的镜头。



1。为什么首先为...添加一个 ...的



JavaScript已经包括 for ... in 构造,可用于迭代对象的属性。但是,它的不是一个forEach循环,因为它枚举了一个对象上的所有属性,并且往往只能在简单的情况。



它在更复杂的情况下分解(包括数组,其使用往往是不鼓励或彻底混淆,需要在中使用正确的数组 )。您可以使用 hasOwnProperty (除其他外)可以解决这个问题,但这有点笨重和不够。



因此,我的假设是添加了结构的,以解决与中的构造,并在迭代时提供更大的实用性和灵活性。人们倾向于将中的视为 forEach 循环,通常可以应用于任何集合并产生正确的导致任何可能的上下文,但这不会发生什么。 循环的循环修复了这个。



我还假设现有的ES5代码运行很重要在ES6下产生的结果与ES5中的结果相同,因此不能对中的的行为进行破坏性更改。



2。 如何工作?



参考文档对本部分有用。具体来说,如果定义了 Symbol.iterator 属性,则对象被认为是 iterable



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



这种方法很有用,因为它提供了自己的迭代器。我可能会说这种方法可能会提出实际问题,因为它依赖于定义一个以前没有的财产,除了我可以告诉的不是这样,因为新的财产基本上被忽略,除非你有意去寻找它它不会出现在 for ...中的循环中作为关键字等)。所以不是这样的。



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



3。为什么对象不是可迭代使用为...的默认情况下



我的猜测是这个组合:


  1. 默认情况下,对象 iterable 可能被认为是不可接受的,因为它添加了之前没有的属性,或者因为对象不一定是一个集合。如Felix所说,迭代函数或正则表达式对象是什么意思?

  2. 可以使用 for ...中的简单对象进行迭代,目前还不清楚内置的迭代器实现可能与行为中现有的的不同/更好。所以即使#1是错误的,并且添加该属性是可以接受的,它可能没有被视为有用的

  3. 想要使对象 iterable 可以轻松地通过定义 Symbol.iterator 属性。

  4. ES6规范还提供了一个地图类型,默认情况下, 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 ...进行迭代,并且可能没有明确的同意默认对象迭代器应该做什么(如果有什么它的确意味着与中的的区别不同),似乎合理的是,对象没有被创建 iterable for 中的重写示例代码:



  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&& amp;& 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/



编辑



为了回应您更新的问题,是的,可以转换 iterable 到数组,使用 spread operator 在ES6。



但是,这似乎在Chrome中似乎没有工作,或至少我无法让它在我的jsFiddle中工作。理论上应该是简单的:

  var array = [... myIterable]; 


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...of would 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.

The for...of statement creates a loop Iterating over iterable objects (including Array, Map, Set, arguments object and so on)...

For example using an 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);
  }
}

The above properly logs data in the order I expect it to when I run the code in Firefox (which supports 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.

For example:

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

or be used with Array methods:

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.

I tried the below code, but I get a 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

解决方案

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 why they have or have not done anything. However, I'll state my assumptions and take my best shot.

1. Why add a for...of construct in the first place?

JavaScript already includes a for...in construct 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.

It breaks down in more complex cases (including with arrays, where its use tends to be either discouraged or thoroughly obfuscated by the safeguards needed to for use for...in with an array correctly). You can work around that by using hasOwnProperty (among other things), but that's a bit clunky and inelegant.

So therefore my assumption is that the for...of construct is being added to address the deficiencies associated with the for...in construct, and provide greater utility and flexibility when iterating things. People tend to treat for...in as a forEach loop that can be generally applied to any collection and produce sane results in any possible context, but that's not what happens. The for...of loop fixes that.

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...in construct.

2. How does for...of work?

The reference documentation is useful for this part. Specifically, an object is considered iterable if it defines the Symbol.iterator property.

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...of simply delegates to the iterator function.

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...in loops as a key, etc.). So that's not the case.

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 iterable using for...of by default?

My guess is that this is a combination of:

  1. Making all objects iterable by 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"?
  2. 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 existing for...in behavior. So even if #1 is wrong and adding the property was acceptable, it may not have been seen as useful.
  3. Users who want to make their objects iterable can easily do so, by defining the Symbol.iterator property.
  4. The ES6 spec also provides a Map type, which is iterable by default and has some other small advantages over using a plain object as a Map.

There's even an example provided for #3 in the reference documentation:

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...in does), it seems reasonable enough that objects were not made iterable by default.

Note that your example code can be rewritten using 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 iterable in the way you want by doing something like the following (or you can make all objects iterable by assigning to Object.prototype[Symbol.iterator] instead):

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/

Edit

And in response to your updated question, yes, it is possible to convert an iterable to an array, using the spread operator in ES6.

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:

var array = [...myIterable];

这篇关于为什么对象不能在JavaScript中迭代?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆