的回顾: 的
好的,自从我问这个问题以来已经有一段时间了。像往常一样,尽管在网上和网上的其他地方都提出了反对它的所有有效论据,我还是去了Object.prototype
。我想我就是那种顽固的混蛋。
我试图提出一种结论性的方法来防止新方法破坏任何预期的行为,这被证明是一个非常艰难但信息丰富的事情。
我学到了很多关于JavaScript的知识。至少我不会像在使用原生原型一样轻率地尝试任何东西(除了String.prototype.trim
以及IE< 9)。
在这种特殊情况下,我不使用任何库,因此冲突不是我主要关注的问题。但是在玩原生原型时,我已经深入研究了可能发生的事故,我不太可能将这些代码与任何lib结合使用。
通过研究这种原型方法,我对模型本身有了更好的理解。我将原型作为一种灵活的传统抽象类处理,使我坚持传统的OOP思维。这个观点并没有真正做到原型模型的正义。道格拉斯·克罗克福德(Douglas Crockford)写到了这个陷阱,遗憾的是,粉红色的背景使我无法阅读整篇文章。
我决定更新这个问题,因为读这篇文章的人很想亲眼看看。我只能这样说:一定要做到。我希望你能像我一样学习一些简洁的东西,然后再决定放弃这个相当愚蠢的想法。一个简单的函数可能也可以正常工作,甚至更好,尤其是在这种情况下。毕竟,真正的美妙之处在于,通过添加3行代码,您可以使用相同的功能来增强特定对象的原型。
我知道我将问一个已存在很长一段时间的问题,但是:为什么Object.prototype被认为是 off limit ?就在那里,它可以像任何其他原型一样进行扩充。那么,为什么你不应该利用这一点。在我看来,只要你知道你在做什么,就没有理由避开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
,但在我的脚本中,我认为它是两个邪恶中的较小者。
也许,有人可以更好地告诉我我错在哪里this chap等等。
在寻找人们给 NOT 触摸Object
原型的原因时,有一件事情不断出现:它打破了for..in
循环,但随后再次:许多框架也会这样做,更不用说你自己的继承链了。因此,在我脑海中循环浏览对象的属性时,不要包含.hasOwnProperty
检查是不好的做法。
我还发现this相当有趣。再说一句:一条评论非常明确:扩展原生原型是不好的做法,但如果V8人这样做,我该说他们错了?我知道,这个论点并没有完全叠加。
关键是:我真的看不到上面代码的问题。我喜欢它,使用它很多,到目前为止,它没有让我失望过一次。我甚至想把更多的函数附加到Object原型上。除非有人能告诉我为什么不应该这样做,就是这样。
答案 0 :(得分:13)
事实是,只要你知道自己在做什么以及花费多少就可以了。但它是大“如果”。费用的一些例子:
您需要使用任何库进行广泛的测试,并选择使用增强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正式EOL了,快速改变。这是因为使用Object.defineProperty
,您可以添加不可枚举的属性(那些未显示在for-in
循环中的属性),这样您就可以减少麻烦(那时,你主要担心名称冲突) - 但它只适用于正确实现Object.defineProperty
的系统(并且正确的实现不能“填充”)。
在您的示例中,我不会将getProperties
添加到Object.prototype
;我将它添加到Object
并接受该对象作为参数,就像ES5对getPrototypeOf
和类似的那样。
请注意,Prototype库在扩展Array.prototype
方面会受到很大影响,因为它会影响for..in
循环。这只是Array
s(你不应该使用for..in
(除非你使用hasOwnProperty
后卫,很可能String(Number(name)) === name
。
......如果V8人这样做,我该说他们错了?
在V8上,您可以依赖Object.defineProperty
,因为V8是完全符合ES5标准的引擎。
请注意,即使属性不可枚举,也存在问题。多年前,Prototype(间接)在filter
上定义了Array.prototype
函数。它完成了你所期望的:调用迭代器函数并根据函数选择的元素创建一个新数组。然后ECMAScript5出现并定义Array.prototype.filter
做同样的事情。但是有一个问题: 很多 同样的事情。特别是,被调用的迭代器函数的签名是不同的(ECMAScript5包含Prototype没有的参数)。它可能比那更糟糕(我怀疑 - 但无法证明 - TC39意识到Prototype并故意避免与它发生太多冲突)。
所以:如果您打算这样做,请注意风险和成本。由于尝试使用现成的库,您可能会遇到的丑陋,边缘情况错误可能真的花费您的时间......
答案 1 :(得分:2)
如果框架和库通常按照您的建议行事,那么很快就会发生两个不同的框架将两个不同的功能定义为Object
(或Array
,{{1}的相同方法} ...或任何现有的对象原型)。因此,最好将这些新功能添加到自己的命名空间中。
例如......想象一下,你会有一个将对象序列化为json的库和一个将它们序列化为XML的库,它们都将其功能定义为
Number
并且您只能使用稍后定义的那个。所以如果他们不这样做会更好,而是
Object.prototype.serialize = function() { ... }
新功能可以在新的ECMAscript标准中定义,也可以由浏览器供应商添加。因此,假设您的浏览器还会添加JSONSerializingLibrary.seralize = function(obj) { ... }
XMLSerializingLibrary.seralize = function(obj) { ... }
函数。这将再次导致与定义相同功能的库发生冲突。即使库的功能与浏览器内置的功能相同,解释的脚本函数也会覆盖原生函数,实际上它会更快。
答案 2 :(得分: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);
});
我主要使用它来在不支持它们的实现上添加标准定义的函数。