2014-10-18 119 views
6

(让我们假设有一个很好的理由希望这一点。如果你想阅读这个好理由,请看问题的结尾)获得与for..in循环相同的结果,没有任何for..in循环

我想获得与for in循环相同的结果,但使用该语言构造的没有。通过结果我的意思是只有一个属性名称的数组(我不需要重现如果我在修改对象的同时迭代它会发生的行为)。

提出这个问题来为代码,我想实现这个功能,而不for in

function getPropertiesOf(obj) { 
    var props = []; 
    for (var prop in obj) 
    props.push(prop); 
    return props; 
} 

从我的ECMAScript的5.1规格大约the for in statementthe Object.keys method的理解,似乎下面的实现应该是正确的:

function getPropertiesOf(obj) { 
    var props = []; 
    var alreadySeen = {}; 

    // Handle primitive types 
    if (obj === null || obj === undefined) 
    return props; 
    obj = Object(obj); 

    // For each object in the prototype chain: 
    while (obj !== null) { 
    // Add own enumerable properties that have not been seen yet 
    var enumProps = Object.keys(obj); 
    for (var i = 0; i < enumProps.length; i++) { 
     var prop = enumProps[i]; 
     if (!alreadySeen[prop]) 
     props.push(prop); 
    } 

    // Add all own properties (including non-enumerable ones) 
    // in the alreadySeen set. 
    var allProps = Object.getOwnPropertyNames(obj); 
    for (var i = 0; i < allProps.length; i++) 
     alreadySeen[allProps[i]] = true; 

    // Continue with the object's prototype 
    obj = Object.getPrototypeOf(obj); 
    } 

    return props; 
} 

的想法是明确行走原型链,并使用Object.keys链中的每个对象以获得自己的属性。我们排除在链中以前的对象中已经看到的属性名称,包括它们被视为不可枚举的属性名称。此方法甚至应该尊重the additional guarantee mentioned on MDN

Object.keys()方法返回给定的对象自身 枚举的属性的阵列,以相同的顺序作为由 for...in提供[...]。

(重点是我的)

我打了一下这个实现了,我一直没能打破它。

所以问题:

是我的分析是正确的?还是我忽略了这个规范的一个细节,会导致这个实现不正确?

你是否知道另一种方式来做到这一点,在所有情况下,这个方法都与for in的具体执行顺序相匹配?

备注:

  • 我不关心的ECMAScript < 5.1。
  • 我不在乎表现(它可能是灾难性的)。

编辑:以满足@ lexicore的好奇心(但问题没有真正的一部分),则很好的理由如下。我开发了一个JavaScript编译器(来自Scala),并且for in语言结构不是我想直接在我的编译器的中间表示中支持的东西的一部分。相反,我有一个“内置”功能getPropertiesOf,这基本上是我作为第一个例子展示的内容。我试图通过用“用户空间”实现(用Scala编写)替换它们来尽可能多地删除内建。对于性能,我仍然有一个优化器,有时会“内化”一些方法,在这种情况下,它将内在地结合getPropertiesOf以及有效的第一个实现。但是为了让中间表示听起来很好,并且在优化器被禁用时工作,我需要真正实现该功能,无论性能成本如何,只要它是正确的。在这种情况下,我不能使用for in,因为我的IR无法表示该构造(但我可以在任何对象上调用任意JavaScript函数,例如Object.keys)。

+0

'(obj === null || obj === undefined)'与'obj == null'相同。出于好奇,是否有一个原因,你使用更详细的形式? – gilly3 2014-10-18 23:34:50

+0

@ gilly3因为'=='是邪恶的。对于一个不太流行的答案:出于同样的原因,我不能使用'for in':IR故意不支持'=='(因为它是邪恶的......)。 – sjrd 2014-10-18 23:38:47

+0

有趣。我觉得'=='实际上是一种应该谨慎使用的语言的力量。 – gilly3 2014-10-18 23:47:56

回答

3

从规格上来看,你只能在假设分析是正确的,一个具体的实现定义枚举的特定顺序对换的说法:

如果实现定义枚举的特定顺序 for-in声明中,必须在此算法的第5步 中使用相同的枚举顺序。

请参阅最后一句here

所以如果执行不提供这样的特定顺序,那么for-in和Object.keys可能会返回不同的东西。那么,在这种情况下,即使两个不同的for-ins可能会返回不同的东西。

非常有趣的是,如果两个for-ins在对象没有改变的情况下会得到相同的结果,那么整个故事就会减少。因为,如果情况并非如此,那么你怎么能测试“相同”呢?

实际上,这很可能是真实的,但我也可以很容易地想象一个对象可以在for-in调用之间动态地重建其内部结构。例如,如果某个属性经常被访问,那么实现可能会重构散列表,以便对该属性的访问更加高效。据我所知,规范并不禁止。而且这也不是很不合理。

所以你的问题的答案是:不,根据规范没有保证,但仍然可能在实践中工作

更新

我觉得还有另外一个问题。它在哪里定义,原型链成员之间的属性顺序是什么?你可以按照正确的顺序获得“自己”的属性,但它们是否按照你所做的方式合并?例如,为什么孩子属性第一和父母的下一个?

+0

对,我明白这一点。但是如果底层的实现不*定义特定的顺序,那么我的函数也满足规范,不是吗?由于在订单方面没有什么可以满足的,任何订单(包括我使用的订单)都没有问题。这个要求基本上是无效的。 – sjrd 2014-10-18 23:06:35

+0

@sjrd你说得对,没有其他方法可以证明。究竟哪个要求是无效的? – lexicore 2014-10-18 23:09:06

+2

要求按照'for in'所做的顺序返回名称。如果底层实现不能保证任何顺序,那么这个要求是无效的。 – sjrd 2014-10-18 23:11:39