javascript - 为什么对象在 JavaScript 中不可迭代?

标签 javascript arrays iterator ecmascript-6 ecmascript-5

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

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

类似 ES6 的语句 for...of 默认情况下用于对象会很好。因为这些功能仅适用于不包括 {} 的特殊“可迭代对象”。对象,我们必须通过箍来使这项工作适用于我们想要使用它的对象。

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



例如使用 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)中运行代码时,上面按照我期望的顺序正确记录数据:

output of hacky for...of

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

另外,因为{}对象不同于“类数组”集合和“可迭代对象”,例如 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属性(property)。

属性定义应该是一个函数,它返回集合中的项目,一个,一个,一个,并设置一个标志,指示是否还有更多的项目要获取。为 some object-types 提供了预定义的实现,而且使用 for...of 比较清楚简单地委托(delegate)给迭代器函数。

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

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

3.为什么对象不是iterable使用 for...of默认情况下?

我的猜测是,这是以下各项的组合:

  • 制作所有对象 iterable默认情况下可能被认为是 Not Acceptable ,因为它添加了一个以前没有的属性,或者因为对象不是(必然)集合。正如 Felix 所指出的,“迭代一个函数或一个正则表达式对象是什么意思”?
  • 简单对象已经可以使用 for...in 进行迭代,并且不清楚内置的迭代器实现可以比现有的 for...in 做得不同/更好。行为。因此,即使 #1 是错误的并且添加属性是可以接受的,它也可能不被视为有用。
  • 想要制作对象的用户 iterable可以很容易地做到这一点,通过定义 Symbol.iterator属性(property)。
  • ES6 规范还提供了一个 Map类型,即 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 所做的有所不同)可能没有明确的协议(protocol),似乎很合理,对象不是 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/

    编辑

    针对您更新的问题,是的,可以转换 iterable到一个数组,使用 spread operator在 ES6 中。

    但是,这似乎还不能在 Chrome 中工作,或者至少我无法在我的 jsFiddle 中使用它。理论上它应该很简单:
    var array = [...myIterable];
    

    关于javascript - 为什么对象在 JavaScript 中不可迭代?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29886552/

    相关文章:

    javascript - 如何使用java脚本验证html文本字段以确保文本字段中不包含超过一个单词

    javascript - 有没有办法让 javascript 不计算特定字符?

    javascript - 正则表达式验证 PIN 码 JS

    javascript - 将数组返回的项目追加到 html

    c++ - 定义不同容器的功能

    javascript - 用于媒体查询的 jQuery 调整大小功能

    c - 如何在事先不知道长度的情况下在 C 中创建数组?我不能假设系统将使用任何特定标准

    c++ - 从文本文件中更改一行中的字段,存储到数组中,然后重新写入文件

    c++ - 为什么 vector::iterator 在重新分配时无效?

    c++ - 删除二维 vector 行中的元素 - 使用迭代器