为什么JavaScript中的对象不可访问?

时间:2015-04-27 03:12:02

标签: javascript arrays iterator ecmascript-6 ecmascript-5

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

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

默认情况下,ES6 for...of等语句可以很好地用于对象。因为这些功能仅适用于特殊的可迭代对象"哪些不包含{}个对象,我们必须通过箍来使这个对我们想要用它的对象起作用。

  

for ... of语句创建一个循环迭代可迭代对象   (包括Array,Map,Set,arguments对象等)......

例如使用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

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

此外,因为{}个对象不同于"类似数组"集合和"可迭代对象"例如NodeListHtmlCollectionarguments,它们无法转换为数组。

例如:

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

7 个答案:

答案 0 :(得分:31)

我会试一试。请注意,我与ECMA没有任何关系并且对决策过程没有任何了解,所以我无法明确地说为什么他们已经或者没有做过任何事情。但是,我会陈述我的假设并尽我所能。

<强> 1。为什么首先添加for...of构造?

JavaScript已经包含一个for...in构造,可用于迭代对象的属性。但是,它是not really a forEach loop,因为它枚举了对象上的所有属性,并且往往只能在简单的情况下以可预测的方式工作。

它在更复杂的情况下崩溃(包括数组,其使用for...in所需的安全措施hasOwnProperty正确使用数组 )。您可以使用for...of(除其他外)来解决这个问题,但这有点笨拙而且不够优雅。

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

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

<强> 2。 iterable如何运作?

discouraged or thoroughly obfuscated对此部分很有用。具体而言,如果对象定义Symbol.iterator属性,则将其视为for...of

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

这种方法很有用,因为它可以非常直接地提供自己的迭代器。我可能会说这种方法可能会产生实际问题,因为它依赖于定义以前没有的属性,除了我可以告诉的情况并非如此,因为新属性基本上被忽略,除非你故意去寻找它(即它不会作为密钥出现在iterable循环中,等等。事实并非如此。

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

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

我的猜测是这是以下内容的组合:

  1. 默认情况下创建所有对象for...in可能被认为是不可接受的,因为它添加了以前没有的属性,或者因为对象不是(必然)集合。正如Felix所说,“迭代函数或正则表达式对象意味着什么?”
  2. 使用for...in已经可以迭代简单对象,并且不清楚内置迭代器实现可以与现有iterable行为不同/更好地完成什么。因此即使#1错误并且添加属性是可以接受的,也可能不会被视为有用
  3. 想要制作对象Symbol.iterator的用户可以通过定义iterable属性轻松完成此操作。
  4. ES6规范还提供some object-types类型,默认情况下 Map,与使用普通对象作为var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; for (var value of myIterable) { console.log(value); } 相比具有一些其他小优势
  5. 参考文档中甚至为#3提供了一个示例:

    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

    ...或者您也可以通过执行以下操作以您想要的方式制作对象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 }; };

    iterable

    你可以在这里玩(在Chrome中,至少):Map

    修改

    在回答您更新的问题时,是的,可以使用ES6中的http://jsfiddle.net/rncr3ppz/5/var array = [...myIterable]; 转换为数组。

    但是,这似乎还没有在Chrome中运行,或者至少我无法让它在我的jsFiddle中运行。理论上它应该如下:

    {{1}}

答案 1 :(得分:7)

我想问题应该是&#34;为什么没有内置对象迭代?

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

一样简单
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

答案 2 :(得分:7)

Object没有在Javascript中实现迭代协议,这是有充分理由的。在JavaScript中有两个级别可以迭代对象属性:

  • 计划级别
  • 数据级别

程序级迭代

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

&#13;
&#13;
const xs = [1,2,3];
xs.f = function f() {};

for (let i in xs) console.log(xs[i]); // logs `f` as well
&#13;
&#13;
&#13;

我们刚刚检查了xs的程序级别。由于数组存储数据序列,我们通常只对数据级别感兴趣。 for..in与数组和其他数据导向相关的显然毫无意义。大多数情况下的结构。这就是ES2015引入for..of和可迭代协议的原因。

数据级别迭代

这是否意味着我们可以通过区分函数和原始类型来简单地将数据与程序级别区分开来?不,因为函数也可以是Javascript中的数据:

    例如,
  • Array.prototype.sort期望函数执行某种排序算法
  • () => 1 + 2这样的Thunks只是懒惰评估值的函数包装器

除了原始值也可以代表程序级​​别:

  • [].length例如是Number,但表示数组的长度,因此属于程序域

这意味着我们只能通过检查类型来区分程序和数据级别。

重要的是要理解普通旧Javascript对象的迭代协议的实现将依赖于数据级别。但正如我们刚刚看到的那样,数据和程序级迭代之间的可靠区别是不可能的。

使用Array时,这种区别是微不足道的:具有类似整数键的每个元素都是一个数据元素。 Object具有等效功能:enumerable描述符。但依靠这个真的可取吗?我相信不是! enumerable描述符的含义太模糊了。

结论

没有有意义的方法来实现对象的迭代协议,因为并非每个对象都是一个集合。

如果默认情况下对象属性是可迭代的,则程序和数据级别会混淆。由于Javascript中的每个复合类型都基于普通对象,因此这也适用于ArrayMap

for..inObject.keysReflect.ownKeys等可用于反射和数据迭代,通常无法明确区分。如果你不小心,你很快就会遇到元编程和奇怪的依赖。 Map抽象数据类型有效地结束了程序和数据级别的混合。我相信Map是ES2015中最重要的成就,即使Promise更令人兴奋。

答案 3 :(得分:3)

您可以轻松地全局迭代所有对象:

Object.defineProperty(Object.prototype, Symbol.iterator, {
    enumerable: false,
    value: function * (){
        for(let key in this){
            if(this.hasOwnProperty(key)){
                yield [key, this[key]];
            }
        }
    }
});

答案 4 :(得分:1)

这是最新的方法(适用于镀铬金丝雀)

var files = {
    '/root': {type: 'directory'},
    '/root/example.txt': {type: 'file'}
};

for (let [key, {type}] of Object.entries(files)) {
    console.log(type);
}

entries现在是Object的一部分方法:)

修改

在进一步了解之后,您似乎可以执行以下操作

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

答案 5 :(得分:1)

我也被这个问题困扰。

然后我想到了使用Object.entries({...})的想法,它返回一个Array的{​​{1}}。

此外,Axel Rauschmayer博士对此发表了很好的答案。 参见Why plain objects are NOT iterable

答案 6 :(得分:0)

从技术上讲,这不是对问题为什么?的答案,但是我根据BT的评论对上述Jack 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);

并不像应该的那样方便,但是它是可行的,尤其是在您有多个对象的情况下:

var instruments={
    'a': 'accordion',
    'b': 'banjo',
    'c': 'cor anglais'
};
Object.defineProperty(instruments,Symbol.iterator,iterableProperties);
for(let v of instruments) console.log(v);

而且,因为每个人都有权发表意见,所以我也看不到为什么对象也不是可迭代的。如果您可以像上面那样填充它们,或者使用for … in,那么我看不到一个简单的参数。

一个可能的建议是,可迭代的是对象的类型,因此,有可能将可迭代性限制为对象的子集,以防万一其他对象在尝试中爆炸。 / p>