JavaScript中哪些对象拥有.length属性?(又名为什么Underscore _.each将我的Function对象视为数组?)

4
我一直以为只有 Array 对象才有 .length 属性。但是,我也看到过提到“类似数组”的对象。我没有深入研究过这个问题,现在看来我的 JS 知识的无知可能会让我吃亏。举个例子:
我有以下代码:
var View = function(options) {
  // code
};

_.extend(View, Backbone.Events, {

    make_children: function(parent) {
      // code
    }

});

后来,我使用这个 View Function 和 Underscore 的 _.each,它决定这个函数对象是一个数组,因为它有一个 .length 属性:
// Code from Underscore.js's `_.each`:
} else if (obj.length === +obj.length) { // This is true
  for (var i = 0, l = obj.length; i < l; i++) { // **So, execution goes here**
    if (iterator.call(context, obj[i], i, obj) === breaker) return
  }
} else {
  for (var key in obj) {
    if (_.has(obj, key)) { // **Execution does __not__ go here**
      if (iterator.call(context, obj[key], key, obj) === breaker) return;
    }
  }
}

这会导致代码不能正常工作,因为i是一个整数索引,在objView上并没有被实际定义。准确来说,在上面的代码中,obj[0]undefined,而obj.length === +obj.lengthtrueobj.length1。到底发生了什么? 附录 Underscore的首席维护者在https://github.com/documentcloud/underscore/pull/510上表示:

简单地将每个拒绝函数对象化并不真正有所帮助。我们已经做出了明智的决定,使用数字长度属性来检测类似于数组的对象。

相反,不要将函数对象传递给each

附录2 意识到我不能传递一个函数对象给_.each,我可以像这样“强制转换”为一个常规对象:
var regular_obj = _.extend({}, View);

3
http://dochub.io/#javascript/length - magritte
1
那么为什么你要将函数对象传递给一个期望数组(或类似数组的对象)的函数呢? - RobG
在你上面的代码中,_.each 被调用在哪里? - Matt Ball
@MattBall: 上述代码中没有被调用。它在别处被调用,在_.contains之后,_.contains使用_.any,而_.any又使用了_.each - Dmitry Minkovsky
@MattBall:我认为这种做法真的非常棒。它是一种中后期绑定,几乎像PHP的静态延迟。 - Dmitry Minkovsky
显示剩余9条评论
2个回答

5
哪些JavaScript对象具有.length属性?
根据显而易见的定义,任何拥有length属性的对象。
这包括函数length是一个函数对象的属性,表示函数期望接受的参数数量,即形式参数的个数。
同时,数组也具有类似的属性,因为它有一个length
var foo = {
    bar: true,
    baz: 'quux',
    length: 42
}

唉,真烦人。很遗憾通过谷歌搜索没有找到答案,可能是我的错。谢谢。 - Dmitry Minkovsky
你有什么建议可以让这个工作起来吗?我应该使用自己的 _.each 类型函数吗? - Dmitry Minkovsky
1
我认为ECMA_262已经很清楚地说明了:“除非另有规定,否则该值等于命名参数的最大数量...包括可选参数”。 - RobG
同意@RobG在您的OP上的评论。我不确定您的问题是否与“Function”具有“length”属性有任何关系。 - Matt Ball

5
问题在于,就像 jQuery 一样,Underscore.js 在其 each 函数中使用 length 属性作为标志。当 length 属性存在时,该函数假定传递的参数可以使用普通 for 循环迭代。这种逻辑背后的原因是期望当定义了 length 属性时,可以依次迭代参数,这就是为什么使用 for 循环的原因。
误用 length 的结果实际上是名称冲突,导致出现意外的结果。我建议将 length 更改为其他同义词,例如 size、capacity 或 totalViews 等。
编辑
如果没有其他选择,必须保留 length,并仍然保留 _.each 的功能,则可以稍微修改它。此插件适用于 Underscore 1.4.3 版本的精简版。
var s = Array.prototype.ForEach;
var r = {};
var myEach = function (n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length&&typeof(n[0])!="undefined"){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(_.has(n,a)&&t.call(e,n[a],a,n)===r)return};
_.each=myEach;

这里有一个演示:http://jsfiddle.net/Xa5qq/ 基本上,它在length属性存在但typeof(yourObject[0]) == "undefined"时使用forEach

1
根据ECMA-262,您不能修改函数的length属性,除非更改其声明中的形式参数数量。您也不能删除它:“在本条款中描述的内置Function对象的length属性具有以下属性{[[Writable]]: false,[[Enumerable]]: false,[[Configurable]]: false}”。 - RobG
@RobG:是的,我通过试错发现了这一点。感谢您的确认:D - Dmitry Minkovsky
@dimadima - 请查看编辑后的 hack,以使用underscore的each方法,如果没有办法防止内置的length属性破坏你的代码。 - Travis J
@dimadima - 如果在您发出_.each之后实例化_,那么这可能是正确的。在这种情况下,您仍应该能够使用_.prototype.each来处理未来的情况。 - Travis J
@TravisJ: 我测试了一下,但指针好像没有改变。当我查看 Underscore 代码时,似乎闭包内部的指针不会因为我在 window._ 中修改指针而发生变化。我认为我测试得没问题。无论如何,这是一个出乎意料的绕路!所有的测试和规范都是这样。有时候它会让人感到疼痛。 - Dmitry Minkovsky
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接