为什么要改变对性能不好的]?

标准的 setPrototypeOf函数的MDN文档以及非标准的__proto__属性 :

无论如何实现这个对象的[[Prototype]],我们强烈build议不要这么做,因为它非常缓慢,不可避免地会减慢现代JavaScript实现中的后续执行。

使用Function.prototype添加属性是将成员函数添加到JavaScript类的方法。 然后如下所示:

 function Foo(){} function bar(){} var foo = new Foo(); // This is bad: //foo.__proto__.bar = bar; // But this is okay Foo.prototype.bar = bar; // Both cause this to be true: console.log(foo.__proto__.bar == bar); // true 

为什么是foo.__proto__.bar = bar; 坏? 如果它的坏不是Foo.prototype.bar = bar; 一样糟糕?

那么为什么这个警告: 这是非常缓慢的,不可避免地减慢了现代JavaScript实现的后续执行 。 当然Foo.prototype.bar = bar; 不是那么糟糕。

更新也许通过突变,他们意味着重新分配。 看到接受的答案。

 // This is bad: //foo.__proto__.bar = bar; // But this is okay Foo.prototype.bar = bar; 

不,两者都做同样的事情(如foo.__proto__ === Foo.prototype ),都很好。 他们只是在Object.getPrototypeOf(foo)对象上创build一个bar属性。

声明所引用的是分配给__proto__属性本身:

 function Employee() {} var fred = new Employee(); // Assign a new object to __proto__ fred.__proto__ = Object.prototype; // Or equally: Object.setPrototypeOf(fred, Object.prototype); 

Object.prototype页面的警告更详细:

由于现代JavaScript引擎如何优化属性访问 ,突变对象的[[Prototype]]是一个非常慢的操作

他们只是说, 改变已经存在的对象的原型链会 导致优化 。 相反,您应该通过Object.create()创build一个具有不同原型链的新对象。

我找不到明确的参考,但是如果我们考虑V8的隐藏类是如何实现的,我们可以看到这里可能会发生什么。 当更改对象的原型链时,其内部types会发生变化 – 它不会像添加属性一样变成子类,而是完全交换。 这意味着所有的属性查找优化都会被刷新,并且预编译的代码将需要被丢弃。 或者它只是回落到非优化的代码。

一些着名的报价:

  • Brendan Eich(你认识他)说

    可写__proto__是一个巨大的痛苦实现(必须序列化循环检查),并创build各种types混淆的危害。

  • Brian Hackett(Mozilla)表示 :

    允许脚本改变几乎任何对象的原型使得难以推断脚本的行为,并使VM,JIT和分析实现变得更加复杂和笨拙。 types推断由于可变的__proto__而有一些缺陷,并且由于这个特性不能保持几个所需的不variables(即,“types集包含所有可能为var / property实现的可能的types对象”和“JS函数具有也是函数的types” )。

  • 杰夫·沃尔登说 :

    创build后的原型突变,其不稳定的性能不稳定,以及对代理和[[SetInheritance]]的影响

  • Erik Corry(Google)表示 :

    我不期望从原始的不可覆盖性中获得巨大的性能收益。 在未优化的代码中,如果原型对象(而不是他们的身份)已被更改,则必须检查原型链。 在优化代码的情况下,如果有人写了proto,你可以回退到非优化的代码。 所以这至less在V8曲轴上不会有太大的变化。

  • Eric Faust(Mozilla)表示

    当你设置__proto__的时候,你不仅会破坏你对Ion的未来优化的任何机会,而且还会迫使引擎绕过所有其他的types推断(关于函数返回值的信息,或者财产价值),也许)认为他们知道这个对象,并告诉他们不要做出许多假设,这涉及到进一步去优化,或许现有的jitcode失效。
    在执行过程中改变对象的原型实际上是一个令人讨厌的大锤,我们必须避免错误的唯一方法是保证安全,但安全是缓慢的。

__proto__ / setPrototypeOf与分配给对象原型不一样。 例如,当你有一个指定了成员的函数/对象时:

 function Constructor(){ if (!(this instanceof Constructor)){ return new Constructor(); } } Constructor.data = 1; Constructor.staticMember = function(){ return this.data; } Constructor.prototype.instanceMember = function(){ return this.constructor.data; } Constructor.prototype.constructor = Constructor; // By doing the following, you are almost doing the same as assigning to // __proto__, but actually not the same :P var newObj = Object.create(Constructor);// BUT newObj is now an object and not a // function like !!!Constructor!!! // (typeof newObj === 'object' !== typeof Constructor === 'function'), and you // lost the ability to instantiate it, "new newObj" returns not a constructor, // you have .prototype but can't use it. newObj = Object.create(Constructor.prototype); // now you have access to newObj.instanceMember // but staticMember is not available. newObj instanceof Constructor is true // we can use a function like the original constructor to retain // functionality, like self invoking it newObj(), accessing static // members, etc, which isn't possible with Object.create var newObj = function(){ if (!(this instanceof newObj)){ return new newObj(); } }; newObj.__proto__ = Constructor; newObj.prototype.__proto__ = Constructor.prototype; newObj.data = 2; (new newObj()).instanceMember(); //2 newObj().instanceMember(); // 2 newObj.staticMember(); // 2 newObj() instanceof Constructor; // is true Constructor.staticMember(); // 1 

每个人似乎都只关注原型,忘记了函数可以让成员分配给它并在变异之后实例化。 目前还没有其他方法可以不使用__proto__ / setPrototypeOf 。 几乎没有人使用一个构造函数而无法从父构造函数inheritance,而Object.create无法提供服务。

再加上,这是两个Object.create调用,目前在V8(浏览器和Node)都是不速慢的,这使得__proto__成为一个更可行的select

是的。原型=同样不好,因此“不pipe它是如何完成的”这个措词。 原型是在类级别扩展function的伪对象。 它的dynamic特性减缓了脚本的执行速度。 另一方面,在实例级别添加一个函数的开销要小得多。

这是一个使用节点v6.11.1的基准

NormalClass :一个普通的类,原型不被编辑

PrototypeEdited :编辑了原型的类(添加了test()函数)

PrototypeReference :添加了原型函数test() ,它引用了外部variables

结果:

 NormalClass x 71,743,432 ops/sec ±2.28% (75 runs sampled) PrototypeEdited x 73,433,637 ops/sec ±1.44% (75 runs sampled) PrototypeReference x 71,337,583 ops/sec ±1.91% (74 runs sampled) 

正如你所看到的,原型编辑类是比普通类更快的方式。 有一个variables指向外部variables的原型是最慢的,但这是一个有趣的方式来编辑已经瞬时variables的原型

资源 :

 const Benchmark = require('benchmark') class NormalClass { constructor () { this.cat = 0 } test () { this.cat = 1 } } class PrototypeEdited { constructor () { this.cat = 0 } } PrototypeEdited.prototype.test = function () { this.cat = 0 } class PrototypeReference { constructor () { this.cat = 0 } } var catRef = 5 PrototypeReference.prototype.test = function () { this.cat = catRef } function normalClass () { var tmp = new NormalClass() tmp.test() } function prototypeEdited () { var tmp = new PrototypeEdited() tmp.test() } function prototypeReference () { var tmp = new PrototypeReference() tmp.test() } var suite = new Benchmark.Suite() suite.add('NormalClass', normalClass) .add('PrototypeEdited', prototypeEdited) .add('PrototypeReference', prototypeReference) .on('cycle', function (event) { console.log(String(event.target)) }) .run()