【问题标题】:Object.prototype is Verboten?Object.prototype 是 Verboten 吗?
【发布时间】:2012-06-01 05:10:28
【问题描述】:

回顾:

好的,我已经有一段时间没有问这个问题了。像往常一样,我还是去增加了Object.prototype,尽管这里和网络上其他地方都有反对它的所有有效论据。我想我就是那种固执的混蛋。

我试图想出一个确凿的方法来防止新方法破坏任何预期的行为,这被证明是一件非常困难但信息丰富的事情。
我学到了很多关于 JavaScript 的知识。至少我不会尝试像弄乱本机原型那样鲁莽的事情(除了String.prototype.trim 用于 IE

在这种特殊情况下,我不使用任何库,因此冲突不是我主要关心的问题。但是在玩弄原生原型时,我对可能发生的事故进行了更深入的研究,因此我不太可能将这段代码与任何库结合起来尝试。

通过研究这种原型方法,我对模型本身有了更好的理解。我将原型视为某种形式的灵活的传统抽象类,使我坚持传统的 OOP 思维。这种观点并没有真正做到原型模型公正。道格拉斯·克罗克福德 (Douglas Crockford) 写过这个陷阱,遗憾的是,粉红色的背景让我无法阅读全文。

我决定更新这个问题,以免阅读本文的人很想亲眼看看。我只能说:无论如何,做。我希望你在决定放弃这个相当愚蠢的想法之前,像我一样学习一些简洁的东西。一个简单的函数可能同样有效,甚至更好,尤其是在这种情况下。毕竟,它的真正美妙之处在于,只需添加 3 行代码,您就可以使用相同的函数来增强特定对象的原型。


我知道我要问一个已经存在了很长一段时间的问题,但是:为什么 Object.prototype 被认为是禁区?它就在那里,并且可以像任何其他原型一样进行增强。那么,为什么不应该利用这一点。在我看来,只要你知道自己在做什么,就没有理由避开 Object 原型。
以这个方法为例:

if (!Object.prototype.getProperties)
{
    Object.prototype.getProperties = function(f)
    {
        "use strict";
        var i,ret;
        f = f || false;
        ret = [];
        for (i in this)
        {
            if (this.hasOwnProperty(i))
            {
                if (f === false && typeof this[i] === 'function')
                {
                    continue;
                }
                ret.push(i);
            }
        }
        return ret;
    };
}

基本上,它与旧的for...in 循环相同,您可以在函数中保持安全,或者一遍又一遍地编写。我知道它将被添加到 all 对象中,并且由于 JavaScript 中的几乎每个继承链都可以追溯到 Object.prototype,但在我的脚本中,我认为它是两害相权取其轻。

也许,有人可以比this chap 更好地告诉我哪里错了。
在寻找人们 接触 Object 原型的原因时,不断出现一件事:它打破了 for..in 循环,但话又说回来:许多框架确实如此,更不用说你自己的继承链了。因此,在我看来,在循环遍历对象的属性时不包括 .hasOwnProperty 检查是不好的做法。

我还发现this 很有趣。再说一遍:有一条评论非常明确:扩展原生原型是不好的做法,但如果 V8 人这样做,我还能说他们错了吗?
我知道,这个论点并不完全成立。

重点是:我看不出上面的代码有什么问题。我喜欢它,经常使用它,到目前为止,它没有让我失望过一次。我什至正在考虑将更多功能附加到对象原型。除非有人能告诉我为什么我不应该这样做。

【问题讨论】:

  • 我从来没有想过这个。但是哪些框架打破了for...in 循环?请详细说明我自己的“继承链”如何打破for...in?我不是想固执己见,我只是不明白这些例子是如何打破for...in 循环的。也许是一个例子?其他框架和自定义继承中断的示例for...in
  • 谁对我的问题投了反对票:请解释一下原因
  • 喜欢更新的部分:D

标签: javascript prototype


【解决方案1】:

事实是,只要您知道自己在做什么以及成本是多少,就可以了。但这是一个“如果”。一些费用示例:

  • 您需要使用 any 库进行广泛测试,您选择在增强 Object.prototype 的环境中使用,因为压倒性的约定是空白对象将没有可枚举的属性。通过将可枚举属性添加到Object.prototype,您使该约定为假。例如,这很常见:

    var obj = {"a": 1, "b": 2};
    var name;
    for (name in obj) {
        console.log(name);
    }
    

    ...压倒性的约定是只有“a”和“b”会出现,而不是“getProperties”。

  • 任何从事代码工作的人都必须接受教育,即不遵守该约定(上述)。

如果支持,您可以通过使用Object.defineProperty(和类似的)来缓解上述问题,但请注意,即使在 2014 年,don't support it 等 IE8 之类的浏览器仍然在大量使用(尽管我们希望现在这种情况会迅速改变) XP 已正式停产)。那是因为使用Object.defineProperty,您可以添加 non-enumerable 属性(那些不会出现在for-in 循环中的属性),因此您的麻烦会少很多(此时,您主要担心名称冲突)——但它仅适用于正确实现 Object.defineProperty 的系统(并且不能“填充”正确的实现)。

在您的示例中,我不会将getProperties 添加到Object.prototype;我会将它添加到 Object 并接受该对象作为参数,就像 ES5 对 getPrototypeOf 和类似的那样。

请注意,Prototype 库因扩展 Array.prototype 而受到很多批评,因为这会影响 for..in 循环。这只是Arrays(无论如何你都不应该使用for..in(除非你正在使用hasOwnProperty保护并且很可能也使用String(Number(name)) === name)。

...如果 V8 人这样做,我还能说他们错了吗?

在 V8 上,您可以依赖 Object.defineProperty,因为 V8 是一个完全符合 ES5 的引擎。

请注意,即使属性不可枚举,也存在问题。多年前,Prototype(间接)在Array.prototype 上定义了一个filter 函数。它会如您所愿:调用迭代器函数并根据函数选择的元素创建一个新数组。然后 ECMAScript5 出现并定义了 Array.prototype.filter 来做同样的事情。但问题是:很多是一样的。特别是,被调用的迭代器函数的签名是不同的(ECMAScript5 包含一个 Prototype 没有的参数)。情况可能比这更糟(我怀疑——但无法证明——TC39 知道 Prototype 并有意避免与其发生过多冲突)。

所以:如果您要这样做,请注意风险和成本。由于尝试使用现成的库而可能遇到的丑陋的极端情况错误可能真的会花费您的时间...

【讨论】:

  • 您好,感谢您的努力。确实是一个非常翔实的答案。回顾一下:通过在我的方法中添加 enumerable: false 并添加 HPB 的建议(针对 V8 引擎浏览器),我相当安全,不是吗?我想补充一点,我没有使用任何库(摆脱了 jQuery),所以那里没有问题,我想......我明天会研究这个,现在是晚上去的时候了 :)跨度>
  • @EliasVanOotegem:这取决于您所说的“相当安全”是什么意思。请参阅最后的原型,因为名称冲突,即使不可枚举也无济于事。如果您要这样做,我会使用某种前缀,以使您不太可能与以后添加的内容发生冲突。
【解决方案2】:

如果框架和库通常按照您的建议进行,那么很快就会发生两个不同的框架将两个不同的功能定义为Object(或ArrayNumber...或任何其他方法)的相同方法现有对象原型)。因此,最好将此类新功能添加到自己的命名空间中。

例如...想象一下,您将有一个将对象序列化为 json 的库和一个将它们序列化为 XML 的库,并且两者都将它们的功能定义为

Object.prototype.serialize = function() { ... }

您只能使用稍后定义的那个。所以他们最好不这样做,而是这样做

JSONSerializingLibrary.seralize = function(obj) { ... }
XMLSerializingLibrary.seralize = function(obj) { ... }

新功能也可能在新的 ECMAscript 标准中定义,或由浏览器供应商添加。所以想象一下你的浏览器也会添加一个serialize 函数。这将再次导致与定义相同功能的库发生冲突。即使这些库的功能与浏览器内置的功能相同,解释的脚本函数也会覆盖本机函数,这实际上会更快。

【讨论】:

  • 感谢您的努力,我看到我没有提到我没有使用任何类型的任何框架/库,所以那里没有冲突。当谈到新的 ES 标准时:我几乎很担心这些,因为我之前处理过类似的问题,但事实证明要绕过它并不难。
【解决方案3】:

http://www.websanova.com/tutorials/javascript/extending-javascript-the-right-way

这解决了一些(但不是全部)提出的反对意见。如果 Object.prototype 中已经存在特定于域的方法,则可以通过引发异常来缓解对不同库创建冲突方法的反对意见。当这种不良事件发生时,这至少会提供警报。

受这篇文章的启发,我开发了以下内容,这些内容也可以在引用页面的 cmets 中找到。

!Object.implement && Object.defineProperty (Object.prototype, 'implement', {
  // based on http://www.websanova.com/tutorials/javascript/extending-javascript-the-right-way
  value: function (mthd, fnc, cfg) { // adds fnc to prototype under name mthd
      if (typeof mthd === 'function') { // find mthd from function source
        cfg = fnc, fnc = mthd;
        (mthd = (fnc.toString ().match (/^function\s+([a-z$_][\w$]+)/i) || [0, ''])[1]);
      }
      mthd && !this.prototype[mthd] && 
        Object.defineProperty (this.prototype, mthd, {configurable: !!cfg, value: fnc, enumerable: false});
    }
});

Object.implement (function forEach (fnc) {
  for (var key in this)
    this.hasOwnProperty (key) && fnc (this[key], key, this);
});

我主要使用它来在不支持它们的实现上添加标准定义的函数。

【讨论】:

  • 好的,我还没有完全测试过这个建议,但我肯定会在一些 kip 之后进一步研究这个问题。感谢您的链接顺便说一句。我不完全确定你的建议是否适用于 IE8,我很遗憾地说,它还没有达到那个时候,它只不过是一个噩梦。
  • 我对你的测试结果很感兴趣。
  • 好的,我又回到了带有 IE8 的 windows 框,并检查了您的建议。不幸的是,IE8 的 JScript 不是 JavaScript,并且不支持 defineProperty 方法。一个快速的谷歌搜索告诉我没有什么,但是使用一个 try-catch 块来支持兼容 ECMA 标准的(又名好的)浏览器和 MS 的 IE 浏览器无用的借口。我想现在我知道为什么首字母缩写词 MS 让我想起一种退化、衰弱和可怕的疾病。但是,我现在明白为什么 Object.prototype 在为旧浏览器开发时会带来麻烦。再次感谢您的链接和努力
猜你喜欢
  • 1970-01-01
  • 2012-12-06
  • 2014-12-07
  • 1970-01-01
  • 2011-06-30
  • 2015-01-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多