为什么扩展本机对象是一种不好的做法?

2020/11/18 12:22 · javascript ·  · 0评论

每位JS意见领袖都说,扩展本机对象是一种不良做法。但为什么?我们会获得出色的表现吗?他们是否担心有人会以“错误的方式”这样做,并向中添加可枚举的类型Object,实际上破坏了任何对象上的所有循环?

TJ Holowaychukshould.js为例。他向其中添加了一个简单的吸气剂Object并且一切正常(源代码)。

Object.defineProperty(Object.prototype, 'should', {
  set: function(){},
  get: function(){
    return new Assertion(Object(this).valueOf());
  },
  configurable: true
});

这真的很有意义。例如,可以扩展Array

Array.defineProperty(Array.prototype, "remove", {
  set: function(){},
  get: function(){
    return removeArrayElement.bind(this);
  }
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);

是否有反对扩展本机类型的论点?

扩展对象时,将更改其行为。

更改仅由您自己的代码使用的对象的行为就可以了。但是,当您更改其他代码也使用的某些行为时,就有可能破坏该其他代码。

当将方法添加到javascript中的对象和数组类中时,由于javascript的工作原理,发生破坏的风险非常高。多年的经验告诉我,这类东西会导致javascript中各种可怕的错误。

如果需要自定义行为,最好定义自己的类(也许是子类),而不是更改本机类。这样,您将不会破坏任何东西。

更改类的工作方式而不对其进行子类化的能力是任何好的编程语言的一项重要功能,但这是很少使用且必须谨慎使用的语言。

没有任何可衡量的缺点,例如性能下降。至少没有人提及。因此,这是个人喜好和经验的问题。

赞成的主要论点:看起来更好,更直观:语法糖。它是特定于类型/实例的功能,因此应专门绑定到该类型/实例。

主要的相反论点:代码可能会干扰。如果库A添加了一个函数,则它可能会覆盖库B的函数。这很容易破坏代码。

两者都有一点。当您依赖两个直接更改类型的库时,由于预期的功能可能不相同,最终很有可能会导致代码损坏。我完全同意。宏库不得操纵本机类型。否则,作为开发人员,您将永远不会知道幕后的真实情况。

这就是我不喜欢jQuery,下划线等库的原因。它们绝对是经过精心编程的,并且像魅力一样运转,但是它们很大您仅使用其中的10%,并了解大约1%。

这就是为什么我更喜欢原子方法,因为您只需要真正需要的东西。这样,您总是知道会发生什么。微库只做您想让他们做的事,因此它们不会干扰。在让最终用户知道添加了哪些功能的情况下,扩展本机类型可以被认为是安全的。

TL; DR如有疑问,请不要扩展本机类型。仅当您100%确定最终用户将了解并希望该行为时,才扩展本机类型。任何情况下都不要操纵本机类型的现有功能,因为它会破坏现有接口。

如果决定扩展类型,请使用Object.defineProperty(obj, prop, desc); 如果不能,请使用类型的prototype


我最初提出这个问题是因为我希望Errors可通过JSON发送。因此,我需要一种对它们进行分类的方法。error.stringify()感觉比更好errorlib.stringify(error); 正如第二种结构所暗示的那样,我是errorliberror自己而不是自己身上做手术。

如果您逐案看待,也许某些实现是可以接受的。

String.prototype.slice = function slice( me ){
  return me;
}; // Definite risk.

覆盖已经创建的方法会带来更多无法解决的问题,这就是为什么在许多编程语言中都普遍采用这种方法来避免这种做法。开发人员如何知道功能已更改?

String.prototype.capitalize = function capitalize(){
  return this.charAt(0).toUpperCase() + this.slice(1);
}; // A little less risk.

在这种情况下,我们不会覆盖任何已知的核心JS方法,但会扩展String。这篇文章中的一个论点提到新开发者如何知道该方法是否是核心JS的一部分,或者在哪里可以找到文档?如果核心JS String对象获得名为capitalize的方法会怎样?

如果您使用所有开发人员都可以理解的公司/应用程序特定修饰符,而不是添加可能与其他库冲突的名称该怎么办?

String.prototype.weCapitalize = function weCapitalize(){
  return this.charAt(0).toUpperCase() + this.slice(1);
}; // marginal risk.

var myString = "hello to you.";
myString.weCapitalize();
// => Hello to you.

如果您继续扩展其他对象,则所有开发人员都会在我们的情况下(在本例中)使用we遇到它们,这会通知他们这是公司/应用程序特定的扩展。

这不会消除名称冲突,但会减少可能性。如果您确定扩展核心JS对象适合您和/或您的团队,那么也许适合您。

我认为这是一种不良做法。主要原因是集成。引用should.js文件:

OMG IT扩展了对象?

那么,作者怎么知道?如果我的模拟框架也一样怎么办?如果我的诺言lib也一样怎么办?

如果您是在自己的项目中执行此操作,那很好。但是对于图书馆来说,这是一个糟糕的设计。Underscore.js是正确执行操作的示例:

var arr = [];
_(arr).flatten()
// or: _.flatten(arr)
// NOT: arr.flatten()

扩展内置原型确实是一个坏主意。但是,ES2015引入了一种新技术,可以用来获得所需的行为:

利用WeakMaps将类型与内置原型相关联

以下实现扩展了NumberArray原型,而完全不涉及它们:

// new types

const AddMonoid = {
  empty: () => 0,
  concat: (x, y) => x + y,
};

const ArrayMonoid = {
  empty: () => [],
  concat: (acc, x) => acc.concat(x),
};

const ArrayFold = {
  reduce: xs => xs.reduce(
   type(xs[0]).monoid.concat,
   type(xs[0]).monoid.empty()
)};


// the WeakMap that associates types to prototpyes

types = new WeakMap();

types.set(Number.prototype, {
  monoid: AddMonoid
});

types.set(Array.prototype, {
  monoid: ArrayMonoid,
  fold: ArrayFold
});


// auxiliary helpers to apply functions of the extended prototypes

const genericType = map => o => map.get(o.constructor.prototype);
const type = genericType(types);


// mock data

xs = [1,2,3,4,5];
ys = [[1],[2],[3],[4],[5]];


// and run

console.log("reducing an Array of Numbers:", ArrayFold.reduce(xs) );
console.log("reducing an Array of Arrays:", ArrayFold.reduce(ys) );
console.log("built-ins are unmodified:", Array.prototype.empty);

如您所见,即使原始原型也可以通过此技术进行扩展。它使用映射结构和Object标识将类型与内置原型相关联。

我的示例启用了一个reduce仅将aArray作为其单个参数的函数,因为它可以从Array本身的元素中提取信息,以了解如何创建一个空的累加器以及如何使用此累加器连接元素。

请注意,我本可以使用普通Map类型的,因为当弱引用仅代表内置的原型(绝不会进行垃圾收集)时,它们是没有意义的。但是,WeakMap除非您拥有正确的密钥,否则a是不可迭代的并且无法检查。这是一个理想的功能,因为我想避免任何形式的类型反射。

为什么扩展本机对象的另一个原因

我们使用Magento,它使用prototype.js并在本机Objects上扩展了很多内容。除非您决定使用新功能,否则这会很好地解决,这才是大麻烦开始的地方。

我们在其中一页上引入了Webcomponents,因此webcomponents-lite.js决定替换IE中的整个(本机)事件对象(为什么?)。当然,这会破坏prototype.js,进而会破坏Magento。(直到发现问题,您可能需要花费大量时间来追查问题)

如果您喜欢麻烦,请继续这样做!

我可以看到不这样做的三个原因(至少是在应用程序内部),其中现有解决方案中只有两个可以解决:

  1. 如果做错了,您会不小心将一个可枚举的属性添加到扩展类型的所有对象中。使用可以轻松解决Object.defineProperty,默认情况下会创建不可枚举的属性。
  2. 您可能会与正在使用的库产生冲突。勤奋可以避免;只需在添加一些东西到原型之前检查您使用的库定义了哪些方法,检查升级时的发行说明,并测试您的应用程序。
  3. 您可能会与本​​机JavaScript环境的将来版本产生冲突。

第三点可以说是最重要的一点。通过测试,您可以确保原型扩展不会与您使用的库引起任何冲突,因为您可以决定要使用的库。假设您的代码在浏览器中运行,则本机对象不是这样。如果您定义Array.prototype.swizzle(foo, bar)今天,明天又将Google添加Array.prototype.swizzle(bar, foo)到Chrome,您最终可能会遇到一些困惑的同事,他们想知道为什么.swizzle的行为似乎与MDN上记录的不一致。

(另请参见有关mootools如何摆弄他们不拥有的原型故事,这迫使ES6方法重命名以避免破坏网络。)

通过为添加到本机对象的方法使用特定于应用程序的前缀可以避免这种情况(例如,用defineArray.prototype.myappSwizzle代替Array.prototype.swizzle),但这有点丑陋。通过使用独立的实用程序函数而不是扩展原型,它同样可以解决。

性能也是一个原因。有时您可能需要遍历键。做这件事有很多种方法

for (let key in object) { ... }
for (let key in object) { if (object.hasOwnProperty(key) { ... } }
for (let key of Object.keys(object)) { ... }

我通常使用for of Object.keys()它做正确的事情,并且相对简洁,无需添加检查。

但是,它要慢得多

优胜劣汰结果

只是猜测Object.keys速度慢的原因是显而易见的,Object.keys()必须进行分配。实际上AFAIK从那以后必须分配所有密钥的副本。

  const before = Object.keys(object);
  object.newProp = true;
  const after = Object.keys(object);

  before.join('') !== after.join('')

JS引擎可能会使用某种不可变密钥结构,以便Object.keys(object)返回对迭代不可变密钥进行迭代的引用,并object.newProp创建一个全新的不可变密钥对象,但是无论如何,它的速度显然要慢15倍

即使检查hasOwnProperty速度也要慢2倍。

所有这些的要点是,如果您具有性能敏感的代码并且需要遍历键,那么您希望能够使用for in而不必调用hasOwnProperty如果您尚未修改,则只能这样做Object.prototype

请注意,如果您使用Object.defineProperty添加的东西无法枚举来修改原型,那么在上述情况下它们不会影响JavaScript的行为。不幸的是,至少在Chrome 83中,它们确实会影响性能。

在此处输入图片说明

我添加了3000个非枚举属性,以试图强制出现任何性能问题。只有30个属性,测试太接近以至于无法确定是否有任何性能影响。

https://jsperf.com/does-adding-non-enumerable-properties-affect-perf

Firefox 77和Safari 13.1在增强和未增强类之间的性能没有差异,也许v8在该区域已得到修复,您可以忽略性能问题。

但是,让我也添加一个故事Array.prototype.smoosh简短的版本是Mootools,这是一个受欢迎的库,它们是自己制作的Array.prototype.flatten当标准委员会试图添加一个本地人时,Array.prototype.flatten他们发现不打破很多站点是不可能的。发现中断的开发人员建议将es5方法命名smoosh为一个玩笑,但人们吓坏了,不明白这是个玩笑。他们定居了flat而不是flatten

这个故事的寓意是您不应该扩展本机对象。如果这样做,您可能会遇到同样的问题,除非您的特定库恰好与MooTools一样流行,否则浏览器供应商不太可能解决您引起的问题。如果您的图书馆确实如此受欢迎,那将是迫使其他所有人解决您引起的问题的一种方式。所以,请不要扩展本机对象

本文地址:http://javascript.askforanswer.com/weishenmekuozhanbenjiduixiangshiyizhongbuhaodezuofa.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!