【问题标题】:JavaScript: What dangers are in extending Array.prototype?JavaScript:扩展 Array.prototype 有什么危险?
【发布时间】:2012-02-10 04:52:11
【问题描述】:

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

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

在扩展Array.prototype 时可能会出现哪些问题,Google 建议不要这样做?

【问题讨论】:

    标签: javascript coding-style prototype


    【解决方案1】:

    我想添加一个额外的答案,它允许在不破坏 for .. in 循环的情况下扩展 Array 原型,并且不需要使用 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);

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

    【讨论】:

      【解决方案2】:

      我相信这个问题值得更新 ES6 答案。

      ES5

      首先,正如许多人已经说过的那样。将原生原型扩展到 shim 或 polyfill 新标准或修复错误是标准做法,并且无害。例如,如果浏览器不支持 .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/

      【讨论】:

        【解决方案3】:

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

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

        1. for..in 可能无法正常工作
        2. 其他人可能还想用相同的函数名扩展 Array
        3. 它可能无法在所有浏览器中正常工作,即使使用 shim。

        现在可以在 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 按预期工作,符号不会被迭代。
        • 方法名称没有冲突,因为符号是作用域的本地符号,并且需要努力检索。

        缺点:

        【讨论】:

        • arr[myMethod]() -- Cantcha 将其包装在一个更简单的调用中?
        • @johnywhy 是的,但如果你这样做(比如添加Array.prototype.f = Array.prototype[ myMethod ]),那么该方法可用于(并可能与)其他库代码发生冲突。
        【解决方案4】:

        大多数人都忽略了这一点。对Array.prototype.filter 之类的标准功能进行填充或填充,使其在旧版浏览器中运行在我看来是个好主意。不要听那些讨厌的人。 Mozilla 甚至在 MDN 上向您展示了如何做到这一点。通常不扩展 Array.prototype 或其他原生原型的建议可能归结为以下之一:

        1. for..in 可能无法正常工作
        2. 其他人可能还想用相同的函数名扩展 Array
        3. 它可能无法在所有浏览器中正常工作,即使使用 shim。

        以下是我的回复:

        1. 您通常不需要在 Array 上使用 for..in。如果你这样做,你可以使用hasOwnProperty 来确保它是合法的。
        2. 只有当你知道你是唯一这样做的人时才扩展本地人当它是标准的东西时,比如Array.prototype.filter
        3. 这很烦人,让我很恼火。旧 IE 有时在添加此类功能时会出现问题。您只需要根据具体情况查看它是否有效。对我来说,我遇到的问题是将Object.keys 添加到 IE7。它似乎在某些情况下停止工作。您的里程可能会有所不同。

        查看这些参考资料:

        祝你好运!

        【讨论】:

        • “你不需要使用 for..in”——根本不要使用它。即使使用hasOwnProperty,您仍然会越过length,这在大多数情况下是没有意义的。
        • @Malvolio 不同意:您不能轻易知道/控制您的 3rd 方库中的内容。例如,for..in 在 Three.js 的 SEA3D 加载器中中断,我对Array.prototype 进行了添加。事实上,three.js 的大部分内容都使用for..in。真的,当心。这些不是必须找到的令人愉快的错误。
        • @mrdoob 这值得研究吗?
        • @NickWiggill,因为 .. in 不是为数组设计的,它是为类似数组的对象(读取关联数组)设计的。你永远不应该使用 for .. in 数组。
        • 使用Object.defineProperty()扩展Array.prototype对象可以避免枚举问题
        【解决方案5】:

        您可以使用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))
        

        【讨论】:

          【解决方案6】:

          Prototype 这样做。这是邪恶的。下面的 sn-p 演示了这样做会如何产生意想不到的结果:

          <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 个字符。

          【讨论】:

          • 他们不是邪恶的,邪恶的部分是你的代码,如果你把它包装在 .hasOwnProperty 中,只会显示它自己的属性,没有冒犯
          • 我认为使用 .hasOwnProperty 遍历数组在语法上并不优雅。
          • 为什么要使用 for ... in 而不是 forEach?不需要拥有自己的属性或弄乱范围变量
          【解决方案7】:

          我会给你要点,用关键句子,来自 Nicholas Zakas 的优秀文章Maintainable JavaScript: Don’t modify objects you don’t own

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

          基本上,不要这样做。即使您的项目永远不会被其他任何人使用,并且您永远不会导入第三方代码,也不要这样做。当你开始尝试与他人和睦相处时,你会养成一个很难改掉的可怕习惯。

          【讨论】:

          • 如果团队同意,扩展主机对象以使其符合标准并没有错。扩展具有非标准属性的宿主对象是另一回事,我同意“不要这样做”
          • 任何你想出的修改宿主对象的理由都可以通过在你自己的对象上实现这个特性来轻松克服。主机环境的文档不包括您的修改。无论“团队”同意什么,这都会引起混乱。进来的新人不知道您的所有主机修改但您告诉他可以更改主机对象怎么办?事情可能会很快破裂。
          • 这种推理实际上不利于 OOP
          • “宿主环境的文档不包括您的修改。” - 什么文档?我们在这里主要讨论的是支持可能符合或可能符合标准的未知网络浏览器,并且只是添加缺少的功能。如果您正在扩展主机对象以使其符合标准(如 Raynos 所述),那么大概就像问题中一样,您首先测试该功能是否已经存在,并且仅在需要时添加您自己的版本。
          • @jsumners,如果 John 或 Jane 已经写了 VeryUsefulObject 但它仍然缺少 exactlyWhatINeed 方法怎么办?
          【解决方案8】:

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

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

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

          Extending Natives and Host Objects

          【讨论】:

            【解决方案9】:

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

            【讨论】:

            • 他们认为他们在迭代,但实际上他们在枚举 :-) 这样的代码已经坏了,没有必要支持无论如何。
            • 当然,这是愚蠢的代码,但仍然需要注意:如果你在原型中添加了一些东西并开始出现奇怪的错误,这可能是因为这个。
            • 我的意思是这个陷阱假设一个人已经掉进了坑里:)
            【解决方案10】:

            您要覆盖的函数可能会被内部 javascript 调用使用,这可能会导致意外结果。这就是制定该指南的原因之一

            例如,我覆盖了数组的 indexOf 函数,它弄乱了使用 [] 访问数组。

            【讨论】:

            • 我怀疑它搞砸了[]。除非你做错了什么
            • 是的,因为 [] 访问现在返回 indexOf 覆盖函数而不是数据。
            • 当您定义自己的indexOf 时,您肯定首先测试该函数不存在吗?仅在浏览器尚不支持时添加您自己的。
            • 这似乎不是扩展原生原型的问题,而是用非标准方法破坏标准方法的问题。
            【解决方案11】:

            扩展原型是一个只能使用一次的技巧。您这样做并且您使用的库也这样做(以不兼容的方式)并且boom

            【讨论】:

            • 这就是为什么我们只使用编写良好的兼容库。
            • 仅当您将“编写良好”定义为包括“不扩展 Array.prototype”时才有效的规则!
            • 当然……这就是写得好的库的定义,写得好的库不会破坏它们不拥有的环境。
            • 这是对原始问题的一个很好的回答:“不要破坏你不拥有的环境”。你不是唯一一个使用 Array 的人——不要乱用它。
            猜你喜欢
            • 1970-01-01
            • 2012-08-30
            • 2012-01-13
            • 1970-01-01
            • 2011-06-20
            • 2015-03-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多