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

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

问题描述

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

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

像 ES6 那样的语句 , HtmlCollectionarguments,它们不能转换成数组.

例如:

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'}};//我希望能够对example"对象使用for...of".//我还希望能够将示例"对象转换为数组.示例[Symbol.iterator] = function* (obj) {for (let key of Object.keys(obj)) {产量 [key, obj[key]];}};for (let [key, value] of example) { console.log(value);}//错误console.log([...例子]);//错误

解决方案

我会尝试一下.请注意,我不隶属于 ECMA,也不了解他们的决策过程,因此我无法明确说明为什么他们做了或没有做任何事情.不过,我会陈述我的假设并尽我所能.

1.为什么要首先添加 for...of 结构?

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

它在更复杂的情况下会崩溃(包括数组,它的使用往往是 不鼓励或彻底混淆for...in 与数组一起使用正确 所需的保护措施).您可以通过使用 hasOwnProperty(除其他外)来解决这个问题,但这有点笨拙和不雅.

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

我还假设现有的 ES5 代码在 ES6 下运行并产生与在 ES5 下相同的结果很重要,因此不能进行重大更改,例如,for...在 构造中.

2.for...of 如何工作?

参考文档 对这部分很有用.具体来说,如果一个对象定义了 Symbol.iterator 属性,那么它就被认为是 iterable.

property-definition 应该是一个函数,它返回集合中的项目,一个,一个,一个,并设置一个标志,指示是否还有更多的项目要获取.为一些对象类型提供了预定义的实现,并且使用for...of 只是委托给迭代器函数.

这种方法很有用,因为它可以非常简单地提供您自己的迭代器.我可能会说这种方法可能会带来实际问题,因为它依赖于定义以前没有的属性,除非我可以说情况并非如此,因为除非您故意去寻找它,否则基本上忽略了新属性(即它不会作为键出现在 for...in 循环中,等等).所以事实并非如此.

撇开实际的非问题不谈,以一个新的预定义属性开始所有对象,或者隐含地说每个对象都是一个集合",这可能在概念上被认为是有争议的.

3.为什么对象不能iterable默认使用for...of?

我的猜测是:

  1. 默认情况下使所有对象iterable 可能被认为是不可接受的,因为它添加了一个以前没有的属性,或者因为一个对象(必然)不是一个集合.正如 Felix 所说,迭代函数或正则表达式对象意味着什么"?
  2. 简单的对象已经可以使用 for...in 进行迭代,目前尚不清楚内置迭代器实现与现有的 for...在 行为中.因此,即使 #1 是错误的并且添加属性是可以接受的,它也可能不被视为有用.
  3. 想要使对象iterable 的用户可以通过定义Symbol.iterator 属性轻松实现.
  4. ES6 规范还提供了一个地图 类型,默认情况下 iterable 并且与使用普通对象作为 Map 相比还有一些其他的小优势.

参考文档中甚至为#3 提供了一个示例:

var myIterable = {};myIterable[Symbol.iterator] = function* () {产量 1;产量 2;产量 3;};for (myIterable 的 var 值) {控制台日志(值);}

鉴于对象可以很容易地iterable,它们已经可以使用for...in 进行迭代,并且对于默认值可能没有明确的共识对象迭代器应该做的(如果它所做的意味着与 for...in 所做的有所不同),似乎很合理,对象没有被 iterable默认.

请注意,您的示例代码可以使用 for...in 重写:

for (let levelOneKey in object) {console.log(levelOneKey);// 例子"console.log(object[levelOneKey]);//{"random":"nest","another":"thing"}var levelTwoObj = object[levelOneKey];for (让 levelTwoKey 在 levelTwoObj ) {控制台日志(levelTwoKey);//随机的"console.log(levelTwoObj[levelTwoKey]);//巢"}}

...或者您也可以通过执行以下操作以您想要的方式使对象 iterable(或者您可以使 all 对象 iterable 通过分配给 Object.prototype[Symbol.iterator] 代替):

obj = {一个:'1',b:{某事:'其他'},c: 4,d:{嵌套:{nestedAgain:true}}};obj[Symbol.iterator] = function() {var 键 = [];var ref = this;for (var key in this) {//注意:可以在这里做hasOwnProperty()等.键.推(键);}返回 {下一个:函数(){如果(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 };} 别的 {返回{完成:真};}},_索引:0,_keys:键,_obj:参考};};

你可以在这里玩(至少在 Chrome 中):http://jsfiddle.net/rncr3ppz/5/

编辑

针对您更新的问题,是的,可以使用 spread operator.

但是,这似乎还不能在 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天全站免登陆