JavaScript:扩展Array.prototype有什么危险?

时间:2012-01-14 03:06:44

标签: javascript coding-style prototype

Google JavaScript样式指南advises against extending the Array.prototype。 但是,我使用Array.prototype.filter = Array.prototype.filter || function(...) {...}作为在不存在它们的浏览器中使用它(和类似方法)的方法。 MDN实际上提供了similar example

我知道Object.prototype问题,但Array不是哈希表。

在延长Array.prototype时,可能会出现哪些问题会让Google提出反对意见?

11 个答案:

答案 0 :(得分:77)

大多数人都错过了这一点。在我看来,填充或填充Array.prototype.filter这样的标准功能,以便它在旧版浏览器中工作 是一个好主意。不要听仇恨。 Mozilla甚至会向您展示如何在MDN上执行此操作。通常,不扩展Array.prototype或其他原生原型的建议可能归结为其中之一:

  1. for..in可能无法正常工作
  2. 其他人可能还想使用相同的函数名称扩展Array
  3. 即使使用垫片,它也可能无法在每个浏览器中正常工作。
  4. 以下是我的回复:

    1. 您通常不需要在数组上使用for..in。如果你这样做,你可以使用hasOwnProperty来确保它是合法的。
    2. 当您知道自己是唯一一个 OR 的人时,只有在Array.prototype.filter这样的标准内容时才会扩展原生代。
    3. 这很烦人,有点咬我。旧IE有时会出现添加此类功能的问题。您只需要根据具体情况查看它是否有效。对我来说,我遇到的问题是将Object.keys添加到IE7。它似乎在某些情况下停止工作。您的里程可能会有所不同。
    4. 查看以下参考资料:

      祝你好运!

答案 1 :(得分:9)

我将从Nicholas Zakas的优秀文章Maintainable JavaScript: Don’t modify objects you don’t own中给出关键句子的要点:

  • 可靠性:“简单的解释是企业软件产品需要一致且可靠的执行环境才能维护。”
  • 不兼容的实现:“修改您不拥有的对象的另一个危险是命名冲突和不兼容的实现的可能性。”
  • 如果每个人都这样做会怎样?:“简单地说:如果团队中的每个人都修改了他们不拥有的对象,那么很快就会遇到命名冲突,不兼容的实施和维护噩梦。“

基本上,不要这样做。即使您的项目永远不会被其他任何人使用,并且您永远不会导入第三方代码,也不要这样做。当你开始尝试与他人玩耍时,你会建立一种可能很难打破的可怕习惯。

答案 2 :(得分:4)

作为Jamund Ferguson答案的现代更新:

  

通常,不扩展Array.prototype或其他原生原型的建议可能归结为其中之一:

     
      
  1. for..in可能无法正常工作
  2.   
  3. 其他人可能还想使用相同的函数名称扩展Array
  4.   
  5. 即使使用垫片,它也可能无法在每个浏览器中正常工作。
  6.   

现在可以使用ES6添加您的方法,在Symbol中缓解分数1.和2.

它使得一个稍微笨拙的调用结构,但添加了一个没有迭代的属性,并且不能轻易复制。

// Any string works but a namespace may make library code easier to debug. 
var myMethod = Symbol('MyNamespace::myMethod');

Array.prototype[ myMethod ] = function(){ /* ... */ };

var arr = [];

// slightly clumsier call syntax
arr[myMethod]();

// Also works for objects
Object.prototype[ myMethod ] = function(){ /* ... */ };

优点:

  • For..in按预期工作,符号不会迭代。
  • 方法名称不会发生冲突,因为符号在范围内是本地的,需要花费很多时间来检索。

缺点:

答案 3 :(得分:3)

在您自己的应用程序代码中扩展Array.prototype是安全的(除非您在数组上使用for .. in,在这种情况下,您需要为此付费并乐趣重构它们。)

在您希望其他人使用的库中扩展本机主机对象并不酷。您无权损坏您自己图书馆中其他人的环境。

要么在lib.extendNatives()之类的可选方法背后执行此操作,要么将[].filter作为要求。

Extending Natives and Host Objects

答案 4 :(得分:2)

有些人使用for ... in循环来遍历数组。如果向原型添加方法,循环也会尝试迭代那个键。当然,你不应该使用它,但有些人还是这样做。

答案 5 :(得分:2)

Prototype这样做。这是邪恶的。以下代码段演示了如何产生意外结果:

<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"></script>
<script language="javascript">
  a = ["not", "only", "four", "elements"];
  for (var i in a)
    document.writeln(a[i]);
</script>

结果:

not only four elements function each(iterator, context) { var index = 0; . . .

,大约5000个字符。

答案 6 :(得分:1)

您可以使用poser库轻松创建一些沙箱。

查看https://github.com/bevacqua/poser

var Array2 = require('poser').Array();
// <- Array

Array2.prototype.eat = function () {
  var r = this[0];
  delete this[0];
  console.log('Y U NO .shift()?');
  return r;
};

var a = new Array2(3, 5, 7);

console.log(Object.keys(Array2.prototype), Object.keys(Array.prototype))

答案 7 :(得分:1)

我想添加一个额外的答案,该答案允许扩展Array原型而不破坏for .. in循环,并且无需使用hasOwnPropery

不要使用这种错误的方法,它会导致原型值出现在for .. in中:

Array.prototype.foo = function() { return 'foo'; };
Array.prototype.bar = function() { return 'bar'; };

let a = [ 1, 2, 3, 4 ];
console.log(`Foo: ${a.foo()}`);
console.log(`Bar: ${a.bar()}`);
console.log('==== Enumerate: ====');
for (let v in a) console.log(v);

相反,将Object.definePropertyenumerable: false一起使用-正是出于这个原因而存在

Object.defineProperty(Array.prototype, 'foo', {
  value: function() { return 'foo'; },
  enumerable: false
});
Object.defineProperty(Array.prototype, 'bar', {
  value: function() { return 'bar'; },
  enumerable: false
});

let a = [ 1, 2, 3, 4 ];
console.log(`Foo: ${a.foo()}`);
console.log(`Bar: ${a.bar()}`);
console.log('==== Enumerate: ====');
for (let v in a) console.log(v);

注意:总的来说,我建议避免使用Array枚举for .. in。但是,这些知识对于扩展枚举适当的类的原型仍然有用!

答案 8 :(得分:0)

扩展原型是一个只能工作一次的技巧。你使用你也使用了一个库(以不兼容的方式)和 boom

答案 9 :(得分:0)

您重写的功能可能被内部javascript调用使用,这可能会导致意外结果。这是指南的原因之一

例如,我覆盖了数组的indexOf函数,并且使用[]搞砸了访问数组。

答案 10 :(得分:0)

我认为这个问题值得更新 ES6 答案。

<强> ES5

首先,正如许多人已经说过的那样。将原生原型扩展到垫片或填充新标准或修复错误是标准做法,并且没有害处。例如,如果浏览器不支持.filter方法if (!Array.prototype.filter),您可以自行添加此功能。实际上,该语言旨在实现此目的,以管理向后兼容性。

现在,您正在考虑因为JavaScript对象使用原型继承,因此扩展像Array.prototype这样的本地对象而不会干扰应该很容易,但直到ES6它才不可行。

与对象不同,您必须依赖并修改Array.prototype以添加自己的自定义方法。正如其他人所指出的那样,这是不好的因为它污染了全局命名空间,可能以意想不到的方式干扰其他代码,有潜在的安全问题,是一个重大的罪恶等。

在ES5中你可以尝试黑客攻击,但这些实现并没有真正有用。有关更深入的信息,我建议您查看这篇内容丰富的帖子:http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

您可以将方法添加到数组,甚至是数组构造函数,但是在尝试使用依赖于length属性的本机数组方法时会遇到问题。最糟糕的是,这些方法将返回原生Array.prototype而不是您闪亮的新子类数组,即:subClassArray.slice(0) instanceof subClassArray === false

<强> ES6

但是,现在使用ES6,您可以使用class结合extends Array继承内置类,从而克服所有这些问题。它保持Array.prototype完整,创建一个新的子类,它继承的数组方法将属于同一个子类! https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/

请参阅下面的小提琴进行演示: https://jsfiddle.net/dmq8o0q4/1/