20

回顾:

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

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

在这种特殊情况下,我不使用任何库,所以冲突不是我主要关心的问题。但是,在使用原生原型时,我更深入地研究了可能出现的事故,我不太可能尝试将此代码与任何库结合使用。

通过研究这种原型方法,我对模型本身有了更好的理解。我将原型视为某种形式的灵活的传统抽象类,使我坚持传统的 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循环,您可以在函数中保持安全,或者一遍又一遍地编写。我知道它将被添加到所有对象中,并且由于 JavaScript 中几乎每个继承链都可以追溯到 . Object.prototype,但在我的脚本中,我认为它是两害相权取其轻。

也许,有人可以比这个小伙子更好地告诉我我错在哪里,等等。
在寻找人们不去触摸 's 原型的原因Object一件事不断出现:它打破了for..in循环,但又一次:许多框架也这样做,更不用说你自己的继承链了。因此.hasOwnProperty,在我看来,在遍历对象的属性时不包括检查是不好的做法。

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

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

4

3 回答 3

14

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

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

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

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

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

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

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

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

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

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

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

所以:如果你打算这样做,请注意风险和成本。由于尝试使用现成的库而可能遇到的丑陋的边缘案例错误可能真的会花费你的时间......

于 2012-05-25T15:57:40.730 回答
3

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

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

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

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

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

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

于 2012-05-25T16:00:02.813 回答
1

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

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

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

!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);
});

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

于 2012-05-25T16:20:24.397 回答