为什么修改JavaScript对象的原型被视为不好的做法?

22

我偶尔会看到一些评论,说修改JavaScript对象的原型是不被赞同的行为?个人认为这并不会成为一个问题。例如扩展Array对象以具有map和include方法或创建更强大的Date方法。

5个回答

28

问题在于原型可以在多个地方进行修改。例如,一个库将向数组的原型中添加map方法,而您自己的代码将添加相同的方法,但目的不同。因此,一个实现将会被破坏。


1
类似的命名空间冲突也会发生在 jQuery 插件 (它们是对 jQuery 本身的猴子补丁) 中: https://dev59.com/eW025IYBdhLWcg3w4qEx - mu is too short
4
在这方面,我认为如果开发人员决定使用多个库却不知道自己代码的情况,那么这是开发人员的责任...但这回答了我的问题,谢谢。 - colourandcode
11
你会期望开发人员了解像JQuery或DoJo这样的大型库的所有内部结构和实现吗?我怀疑他们是否能够在下一个框架发布之前实现任何东西,然后又不得不重新开始。库和框架的整个意义是你只需要知道如何使用它们。 - James Anderson
1
很难知道某个甚至一个库的所有方法和特性,尤其是那些将在未来版本中引入的方法。因此,为了确保代码的一致性,最好的方法就是避免污染任何全局作用域,其中之一就是标准对象的原型。 - bjornd
1
你不需要知道所有的方法和特性... 重要的是要知道是否操作了原生对象。对于像jQuery或Dojo这样的大型库,这些信息很容易获取。 - colourandcode
显示剩余5条评论

11
由于命名空间冲突的原因,大多数情况下会出现问题。我知道Prototype框架在保持与本地框架不同的名称方面遇到了许多问题。
提供实用工具给用户的主要方法有两种..
原型设计
将函数添加到对象的原型中。MooTools和Prototype都采用这种方法。
优点:
1.非常容易访问。
缺点:
1.可能使用大量系统内存。虽然现代浏览器只从构造函数获取属性的实例,但一些旧浏览器为每个构造函数的每个实例存储一个单独的属性实例。 2.不一定总是可用。
我所说的“不可用”是指:
想象一下,你有一个来自document.getElementsByTagName的NodeList,并想要迭代它们。你不能这样做..
document.getElementsByTagName('p').map(function () { ... });

因为它是一个NodeList,而不是一个Array。上面的代码会导致类似以下的错误:Uncaught TypeError: [object NodeList] doesn't have method 'map'

我应该指出,有非常简单的方法可以将NodeList和其他类数组对象转换为真正的数组。

收集

创建一个全新的全局变量并在其上堆积实用程序。jQuery和Dojo都采用此方法。

优点:

  1. 始终可用。
  2. 内存使用低。

缺点:

  1. 位置不够好看。
  2. 有时使用起来可能感觉笨拙。

使用这种方法,您仍然无法做到..

document.getElementsByTagName('p').map(function () { ... });

..但你可以做到..

jQuery.map(document.getElementsByTagName('p'), function () { ... });

但是正如 Matt 指出的那样,在通常情况下,您会使用上述方法来执行操作。

jQuery('p').map(function () { ... });

哪种更好?

最终,这取决于你。如果你可以接受被覆盖或覆盖他人的风险,那么我强烈推荐原型设计。它是我喜欢的样式,我认为风险值得结果。如果你对此不像我那么确定,那么收集也是一种很好的样式。它们都有优缺点,但总的来说,它们通常会产生相同的最终结果。


1
@tylerwashburn: 真的吗?据我所知,将某些内容添加到原型中只会在构造函数中添加一次。随后实例将从构造函数的原型中获取信息。 - KooiInc
@KooiInc 我不完全确定这是真的,但我听说旧版浏览器会这样做。我会更新我的答案来说明这一点。 - McKayla
@Matt 这样做是为了与原型示例保持一致性。在实际操作中,那是更好的做法。 - McKayla
@tylerwashburn:我从未听说过这个。你能提供一个提到它的链接吗?现在来看第二部分(不一定总是可用的):在什么情况下,Object.prototype的扩展可能不可用? - KooiInc
@RobG:是的,我太蠢了。我想到了一个Object.prototype扩展,毕竟这是OP问题的主题。'Object.prototype.hey = 'hey'; alert('hi'.hey);' 会警报'hey',(213).heynew SomeHomeBrewObject().hey[].hey等也是如此。顺便说一下,这可能是避免过多Object.prototype增强的另一个相当有效的论据。 - KooiInc
显示剩余9条评论

6

正如bjornd指出的那样,猴子补丁只在涉及多个库时才是问题。因此,如果您正在编写可重用的库,则不建议这样做。然而,在使用javascript中的主机对象时消除跨浏览器兼容性问题仍然是目前最好的技术。

请参见2009年的这篇博客文章(或Wayback Machine原始网页), 一个真实的事件说明了当prototype.js和json2.js一起使用时会发生什么。


4

3
除了其他答案之外,修改内置对象可能会导致更长久的问题,如果非标准的更改在足够多的网站上使用,那么未来的ECMAScript版本将无法使用相同名称定义原型方法。见这里

这正是Array.prototype.flattenArray.prototype.contains发生的情况。简而言之,为这些方法编写了规范,它们的提议到达了阶段3,然后浏览器开始发布它们。但是,在两种情况下,发现有古老的库用自己的方法与新方法相同的名称修补了内置的Array对象,并具有不同的行为;结果,网站崩溃,浏览器不得不退出实现新方法,规范也必须进行编辑。(方法被重命名。)

例如,目前有一个关于String.prototype.replaceAll的提案。如果您发布了一个广泛使用的库,并且该库monkeypatches一个自定义的非标准方法到String.prototype.replaceAll,则replaceAll名称将不再可供规范编写者使用;在浏览器可以实现它之前,必须更改它。

1
如果我覆盖了replaceAll方法,但它使用与常规replaceAll相同的方法,并且使用完全相同的参数,那么最终结果会有什么区别吗?如果没有出现任何问题,那么你只是拥有一个早期实现吗? - SwiftNinjaPro
1
如果您实现的版本完全符合规范,那么这是非常好的 - 这被称为polyfill,并且非常普遍。 - CertainPerformance

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