JavaScript中的原型对象破坏了jQuery?

39
我已经在我的页面中添加了一个简单的.js文件,其中包含一些非常普通的常见任务函数,这些函数已添加到Object和Array原型中。
通过试错,我发现将任何函数添加到Object.prototype中,无论它的名称或功能是什么,都会导致jQuery中出现Javascript错误:
罪魁祸首是什么呢?
Object.prototype.foo = function() {
    /*do nothing and break jQuery*/
};

我遇到了一个错误,出现在jquery-1.3.2.js的第1056行,在attr:function { }声明中:

/*Object doesn't support this property or method*/
name = name.replace(/-([a-z])/ig, function(all, letter) {
            return letter.toUpperCase();
        });

显然,G.replace未定义。

虽然很明显我在原型方面有些不理解的地方,但我仍然无法搞清楚是什么。

需要澄清的是,我不是在寻找解决方法,我已经处理了... 我要找的是一个答案:为什么?为什么将函数添加到Object.prototype会破坏这段代码?


3
如果你切换到完整的 jQuery 文件(非压缩文件),可能会得到一个更有意义的错误提示。这样,你就能更清楚地看到哪些代码出了问题。 - Frank DeRosa
@CrescentFresh的链接已经过时。更新链接:https://bugs.jquery.com/ticket/2721 - WoodrowShigeru
5个回答

47
如果仅仅是对于 for...in 循环的使用出现了问题,那么你可以使用 Object.defineProperty() 方法添加 fn 的属性,但并不将其设置为可枚举属性。
所以代码如下:
Object.defineProperty(Object.prototype, "foo", { 
    value: function() {
        // do stuff
    },
    enumerable : false
});

对我来说似乎有效。这仍然被认为是不好的形式吗?


1
我不知道... 如果这是不好的形式,了解一下会很好,但是对我来说这很有效。 - Paul Carlton
2
你可以这样做,但是 for..in 循环被设计用于枚举原型扩展。这就是你探索原型链的方式。它不会破坏 for..in 循环,而是会破坏那些盲目假设迭代值始终是某种类型的错误代码,因为 Object.prototype 链可以包含函数,所以花括号之间的代码可能会在期望仅有标量和对象时抛出异常。 - Marcus Pope
1
@NoOne 我认为扩展Object原型与任何其他原型没有什么不同。只是必须要负责任地进行。Web的开源性质,以及所有可用的框架、插件和工具,意味着您的项目很可能比您自己的代码更多是第三方代码。JavaScript的核心原则之一是不要修改您不拥有的任何东西。如果您只是不加思考地扩展Object,可能会破坏某些期望它以特定方式运行的内容。 - nicholas
1
@NoOne 你的应用程序可能看起来像这样:核心 JavaScript(由浏览器拥有)- > 工具包(如 jQuery,underscore等)- > 框架(backbone,angular,react等)- > 你的应用程序代码。每个层次都依赖于之前的层次以某种方式运作。在任何语言中,向后回溯并更改核心数据类型的工作方式始终是一个问题。 - nicholas
1
@NoOne,在你的应用程序代码中,有很多方法可以获得所需的功能。您可以使用像underscore.string这样的库,使用Object.create(),mixins或工厂方法来构建对象中的功能。一本涵盖了许多关于此技术的好书是JavaScript Patterns(http://www.amazon.com/JavaScript-Patterns-Stoyan-Stefanov/dp/0596806752)。 - nicholas
显示剩余6条评论

20

不要扩展 Object.prototype。这会远远超出破坏 jQuery 的范畴,它完全破坏了 Javascript 中“对象作为哈希表”的特性。请勿这样做。

您可以问问 John Resig,他会告诉您同样的事情:链接


21
扩展Object.prototype是可以的,但需要在for..in循环中使用hasOwnProperty。这在包括Safari 2.0在内的所有主流浏览器中都得到支持。只是jQuery在其for..in循环中没有这样做,这只是因为懒惰。性能影响可以忽略不计,而Resig也知道这一点:http://osdir.com/ml/jquery-dev/2009-02/msg00543.html。这只是我的看法。 - Crescent Fresh
3
@Crescent,这个问题比那复杂得多。当然,你可以通过像 for...in 循环那样解决问题,但是在 JavaScript 中使用对象作为哈希表还有许多其他用途。例如,toStringvalueOf 和其他方法都不会被枚举。这确实有影响。此外,当你是一个被大量人使用的库的首席开发者时,你不能把他的决定归咎于懒惰。我认为更好的词应该是谨慎。 - Josh Stodola
3
@JoshStodola - 实际上不是懒惰,而是无知(大多数人不知道如何使用hasOwnProperty...)在jQuery早期的日子里Resig只是不知道要这么做。不久之后意识到这个缺陷后,他一直认为修复是必要的未来实现,但遗憾的是它从未被纳入核心代码。 - Marcus Pope
1
@CrescentFresh 非常感谢!!!我已经寻找那篇文章好几个月了!(我是那篇文章/代码差异的作者,也是与 Resig 合作修复 jQuery 中的错误的人。)我以为它永远消失在虚空中了。更新一下,我很遗憾地报告,几个月前 Resig 正式关闭了该问题的工单,表示不会修复。这个缺陷在 UI 库中太普遍了,而且坏的编码习惯在社区中也太普遍了。这就是为什么我不再使用 jQuery 的原因 :( 但 Sizzle 兼容 Object.prototype 扩展,这才是我最喜欢 jQuery 的部分。 - Marcus Pope
4
在我看来,这个答案现在已经不正确且过时了。在 ES5 中,你可以使用 Object.defineProperty 并使用默认设置创建一个 非可枚举 的属性,安全地扩展 Object.prototype。请注意,不要更改原意。 - Alnitak
显示剩余3条评论

4

我同意,在Object.prototype中添加内容需要谨慎,应该避免。可以考虑其他解决方案,比如:

将其添加到Object中,然后根据需要使用callapply进行访问。 例如:

Object.foo = function () { return this.whatever()}

然后通过以下方式在对象上调用它:

Object.foo.call(Objname);  // this invokes the function as though it were a
                           // method of Objname.  That is, its like Objname.foo()

为了好玩,你可以添加以下内容(是的,我知道这有点危险...):

Function.using = Function.call; // syntactic sugar

现在,您可以编写Object.foo.using(Objname),它看起来像一个句子。

但是作为一条规则,请避免修改任何大型原型。


下投票,因为这并没有解释为什么扩展对象原型是有问题的(这就是问题),而且建议的解决方案有点繁琐(例如,您无法调用arbitraryObject.foo(),这正是OP在这里尝试实现的主要内容)。 - HappyDog

2

我曾经为了实现所有对象的“真正”面向对象而苦恼不已,就像这样:

interface Object
{
    GetType: () => string;
    ToString: () => string;
    GetHashcode: () => number;
    Equals: (obj: any) => boolean;
}

由于Object.prototype破坏了JQuery,我默认使用上述解决方案来利用defineProperty,但它不接受任何参数。

好消息是你可以对defineProperty进行修改以接受参数。这是我的实现方式:

Object.defineProperty(Object.prototype, "Equals",
    {
        value: function (obj: any)
        {
            return obj == null && this == null
                    ? true
                    : obj != null && this == null
                        ? false
                        : obj == null && this != null
                            ? false
                            : this.GetHashcode() == obj.GetHashcode();
        },
        enumerable: false
    });

这个功能可以正常工作,而且不会与JQuery发生冲突。


这似乎是最佳答案。也不会与谷歌地图 API 冲突。 - vaid

-1

我怀疑在Object.prototype中添加一个函数不会直接破坏jQuery。只需确保您网站中的每个for..in循环都包含hasOwnProperty检查,因为您已经全局添加了该函数,对其进行迭代的结果可能是不可预测的:

Object.prototype.foo = function() {};    
var myObject = {m1: "one", m2: "two" };

for(var i in myObject) { if(myObject.hasOwnProperty(i)) {
   // Do stuff... but not to Object.prototype.foo
}}

如果我注释掉Object.prototype.foo声明,一切都能正常工作。而且,在它出问题的那个点,它甚至还没有到达那个foo声明之后的任何我的代码。 - Ben Lesh
1
你说得对,它并不是直接破坏jQuery,而是破坏了Javascript! - Josh Stodola
这取决于你的看法。理想情况下,你应该能够毫无问题地扩展Object,但实际上,是的,这是一个坏主意,并且很少有好的理由支持它。Crockford认为枚举添加到原型中的函数是“语言中的错误”,因此最佳实践是要保守并始终在for..in循环中添加hasOwnProperty。虽然很烦人,但我会一直这样做 ;) - jshalvi
3
是的,它会破坏jQuery,jQuery将遍历您的Object.prototype扩展,但假设它不是一个函数。当该函数出现在迭代中时,会抛出异常。这不会破坏JavaScript,它非常重要于JavaScript的设计。@jshalvi - 您可以始终创建像jQuery的$.each一样的迭代器函数,并仅编写一次。Crockford 对语言有一些误解,但这并不完全是他的错,他开拓了野性西部的JavaScript领域,在旅程中只犯了一些小错误:D - Marcus Pope

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