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

boo*_*box 72 javascript arrays iterator ecmascript-5 ecmascript-6

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

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

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

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

例如,使用ES6 生成器功能:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

当我在Firefox(支持ES6)中运行代码时,上面按照我期望的顺序正确记录数据:

hacky的输出

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

此外,由于{}对象是从"阵列状"集合和"可迭代的对象",如不同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
Run Code Online (Sandbox Code Playgroud)

aro*_*oth 32

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

1.为什么首先添加一个for...of构造?

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

它在更复杂的情况下发生故障(包括数组,其使用往往不鼓励或通过正确使用for...in数组所需的安全措施进行彻底的混淆).你可以通过使用(除其他外)来解决这个问题,但这有点笨拙和不优雅. hasOwnProperty

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

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

2. for...of工作怎么样?

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

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

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

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

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

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

  1. iterable默认情况下创建所有对象可能被认为是不可接受的,因为它添加了以前没有的属性,或者因为对象不是(必然)集合.正如Felix所说,"迭代函数或正则表达式对象意味着什么"?
  2. 简单对象已经可以使用迭代了for...in,并且不清楚内置迭代器实现可以以不同于现有for...in行为的方式做得更好/更好.因此,即使#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);
}
Run Code Online (Sandbox Code Playgroud)

鉴于可以轻松地创建对象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"
    }
}
Run Code Online (Sandbox Code Playgroud)

...或者您也可以iterable通过执行以下操作以您想要的方式制作对象(或者您可以通过分配来制作所有对象):iterableObject.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
    };
};
Run Code Online (Sandbox Code Playgroud)

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

编辑

并且响应您更新的问题,是的,可以iterable使用ES6中的扩展运算符将数据转换为数组.

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

var array = [...myIterable];
Run Code Online (Sandbox Code Playgroud)

  • 好点再次强调并非每个对象都是一个集合.对象已经被使用了很长时间,因为它非常方便,但最终它们并不是真正的集合.这就是我们现在所拥有的"地图". (2认同)

小智 7

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

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

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

然后

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

a 1
b 2
Run Code Online (Sandbox Code Playgroud)


小智 7

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

  • 程序级别
  • 数据级别

程序级迭代

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

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

for (let i in xs) console.log(xs[i]); // logs `f` as well
Run Code Online (Sandbox Code Playgroud)

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

数据级迭代

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

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

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

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

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


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

对于Arrays,这种区别是微不足道的:具有类似整数键的每个元素都是数据元素.Objects有一个等价的特征:enumerable描述符.但依靠这个真的可取吗?我相信不是!enumerable描述符的含义太模糊了.

结论

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

如果默认情况下对象属性是可迭代的,则程序和数据级别会混淆.由于在Javascript中每个复合类型是基于原生的对象,这将适用于ArrayMap为好.

for..in,Object.keys,Reflect.ownKeys等等可用于反射和数据迭代中,有明显的区别是经常不可能的.如果你不小心,你很快就会遇到元编程和奇怪的依赖.的Map抽象数据类型有效地结束程序和数据电平的混为一谈.我相信这Map是ES2015中最重要的成就,即使Promises更令人兴奋.

  • +1,我认为"没有有意义的方法来实现对象的迭代协议,因为不是每个对象都是一个集合." 总结. (3认同)
  • 我认为这不是一个好的论点。如果您的对象不是集合,为什么要尝试循环它?并非每个对象都是集合,这并不重要,因为您不会尝试迭代不是集合的对象。 (2认同)

Rom*_*mko 6

我也被这个问题困扰。

然后我想出了一个使用的想法Object.entries({...}),它返回一个Array是一个Iterable.

此外,Axel Rauschmayer 博士对此发表了出色的回答。请参阅为什么普通对象不可迭代