我已经阅读了一些文章,建议在JavaScript中扩展内置对象是一个坏主意。比方说,我将first
函数添加到Array
...
Array.prototype.first = function(fn) {
return this.filter(fn)[0];
};
很好,所以现在我可以根据谓词得到第一个元素。但是当ECMAScript-20xx决定首先添加到规范并以不同方式实现时会发生什么? - 好吧,突然之间,我的代码假设一个非标准的实现,开发人员失去信心等等。
那么我决定创建自己的类型......
var Enumerable = (function () {
function Enumerable(array) {
this.array = array;
}
Enumerable.prototype.first = function (fn) {
return this.array.filter(fn)[0];
};
return Enumerable;
}());
现在,我可以将一个数组传递给一个新的Enumerable,然后首先在Enumerable实例上调用。大!我已经尊重ECMAScript-20xx规范,我仍然可以做我想做的事。
然后发布了ES20XX + 1规范,它引入了Enumerable
类型,它甚至没有第一种方法。现在发生了什么?
本文的关键归结为此;扩展内置类型有多糟糕,以及如何在未来避免实现冲突?
注意:使用命名空间可能是处理此问题的一种方法,但话说再说一遍,它不是!
var Collection = {
Enumerable: function () { ... }
};
当ECMAScript规范引入Collection
时会发生什么?
答案 0 :(得分:14)
这正是您必须尽可能少地污染全局命名空间的原因。完全避免任何类型冲突的唯一方法是在IIFE中定义所有内容:
(function () {
let Collection = ...
})();
当且仅当您需要定义全局对象时,例如因为您是一个库并且您希望被第三方使用,您应该定义一个极不可能发生冲突的名称,例如因为它& #39;你的公司名称:
new google.maps.Map(...)
每次定义一个全局对象(包括现有类型的新方法)时,您都会冒一些其他库或某些未来ECMAScript标准的风险,试图在将来某个时候共同选择该名称。
答案 1 :(得分:6)
第一种方法的问题是您在页面上扩展所有脚本的原型。
如果您要包含依赖于新的原生ES-20xx Array.prototype.first
方法的第三方脚本,那么您可以使用您的代码打破它。
第二个例子只是一个问题,如果你要使用全局变量(我希望你没有这样做......)。然后可能会出现同样的问题。
问题是spec authors are increasingly wary of not destroying the existing web,因此如果现有网站太多,他们必须重命名未来的功能。 (这也是为什么你不应该为尚未最终确定的网络平台规范创建polyfill的原因,BTW)
如果您创建扩展本机对象并由100或1000个网站使用的库或框架,则问题肯定会更大。那是你开始阻碍标准过程的时候。
如果您在函数或块范围内使用变量,并且不依赖于将来的功能,那么您应该没问题。
功能范围示例:
(function() {
var Enumerable = {};
})();
阻止范围示例:
{
const Enumerable = {};
}
答案 2 :(得分:3)
这里的危险比未来的ES20xx标准更加微妙。至少在那里你可以删除你自己的代码,并处理任何潜在的行为差异的影响。
危险在于您和其他人都决定使用不一致的行为扩展内置类型 。
考虑这样的事情
// In my team's codebase
// Empty strings are empty
String.prototype.isEmpty = function() { return this.length === 0; }
// In my partner team's codebase
// Strings consisting any non-whitespace chars are non-empty
String.prototype.isEmpty = function() { return this.trim().length === 0; }
您有两个同等有效的isEmpty
定义,最后一个定义为胜利。想象一下,当你的单元测试通过,你的合作伙伴团队的单元测试通过后,你将会遇到一年的痛苦,但你不能在一个网页中合并你的代码库,而不会发生真正奇怪的崩溃,具体取决于哪些库被加载持续。这是可维护性噩梦。
然后你和你的伙伴团队进入一个房间并争论六个小时关于isEmpty
的定义是“正确的”,将做出任意决定,你们两个中的一个到达查看代码库中isEmpty
的每个实例,以确定如何使其与新函数一起使用。你可能会在这个过程中引入一些新的错误。
然后当你发现你们都希望在数字上有一个toInteger
函数但又不想在公司范围内举行关于如何处理负数的会议时,再次重复整个过程
// -3.1 --> -4
Number.prototype.toInteger = function() { return Math.floor(this); }
// -3.1 --> -3
Number.prototype.toInteger = function() { return Math.trunc(this); }
答案 3 :(得分:2)
警告:有意见的回答。
我不相信扩展JavaScript对象是“邪恶的”。显然存在危险,你需要了解这些(如你所知)。我对该陈述的唯一警告是,如果你这样做,你必须引入一种警告冲突的方法。
换句话说,不应该简单地在数组原型上定义first
函数,而应首先看看是否定义了Array.prototype.first
,如果是,则抛出(或者警告自己其他一些方式)。只有在未定义的情况下,您才允许您的代码定义它,或者您将替换每个人的定义。
答案 4 :(得分:1)
作为所有呈现的惊人答案的替代方案,您可以像polyfills那样做。
通常情况下,当有人写出与原型混淆的东西时,会检查是否已经定义了值。
以下是一个例子:
if(!Array.prototype.first) {
Array.prototype.first = function(fn) {
return this.filter(fn)[0];
};
}
这适用于很小的代码,但可能会给大型库带来问题 实现一个匿名的自调用函数可以帮助解决这个问题,如下所示:
(function(){
// Leave if it is already defined
if(Array.prototype.first) {
return;
}
Array.prototype.first = function(fn) {
return this.filter(fn)[0];
};
})();
这有利于之前提供的所有解决方案。
如果您想减少下载代码的占用空间,可以动态加载所需的脚本
如果创建了新的Enumerable
,您可以使用浏览器拥有的E
,如果没有,则可以使用您自己的2D
。
这极其依赖于您的代码库。
答案 5 :(得分:-1)
这个问题和这些答案看起来非常ECMA5而不是6.当ECMA6带来它时,原型的使用是非常无用的。没有太多的理由不添加到内置的原型中,但大多数情况下更多的是,“如果你能避免它,你应该'。
首先,你可能会在添加到内置类型时破坏旧浏览器,也可能会破坏'for(i in array)'格式,因为在基本级别上调用了... in,它可能有意想不到的结果。
我认为,主要原因是ECMA6中有一个非常好的替代方案,即子类化。不要使用任何原型,只需使用:
class myArray extends array {
constructor() { ... }
getFirst() {
return this[0];
}
// Or make a getter
get first() {
return this[0];
}
}
你可以在getter中放置你需要的任何函数,但它仍然是不会侵入未来命名空间的独立代码。但是,因为您是子类,所以您不必重写数组。因此,它不仅仅是纯粹的邪恶,而是更好的编码而不是这样做,就像在一个长页面中编写所有javascript并不一定是邪恶的一样,但是如果你能避免它,那么它是更好的标准做法。