Javascript 为什么改变对象的[[prototype]]会对性能造成不良影响?

Javascript 为什么改变对象的[[prototype]]会对性能造成不良影响?,javascript,performance,prototype,prototype-chain,Javascript,Performance,Prototype,Prototype Chain,来自标准和非标准的MDN文档: 无论如何实现,都强烈反对对对象的[[Prototype]]进行变异,因为在现代JavaScript实现中,它非常缓慢,并且不可避免地会减慢后续执行 使用Function.prototype添加属性是向javascript类添加成员函数的方法。然后如下图所示: function Foo(){} function bar(){} var foo = new Foo(); // This is bad: //foo.__proto__.bar = bar; //

来自标准和非标准的MDN文档:

无论如何实现,都强烈反对对对象的[[Prototype]]进行变异,因为在现代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.\uuuu proto\uuuuu.bar=bar坏?如果它的坏不是
Foo.prototype.bar=bar同样糟糕

那么为什么会出现这样的警告:它非常慢,在现代JavaScript实现中不可避免地会减慢后续执行。当然
Foo.prototype.bar=bar没有那么糟糕


更新也许突变意味着重新分配。见公认的答案

是的。prototype=同样糟糕,因此使用了“无论如何完成”的措辞。prototype是一个伪对象,用于在类级别扩展功能。它的动态特性会减慢脚本的执行速度。另一方面,在实例级别添加一个函数会带来更少的开销。

是的。prototype=同样糟糕,因此使用了“不管它是如何完成的”这一措辞。prototype是一个伪对象,用于在类级别扩展功能。它的动态特性会减慢脚本的执行速度。另一方面,在实例级别添加一个函数会带来更少的开销

不。两者都在做相同的事情(如
foo.\uuu proto\uuu==foo.prototype
),而且都很好。他们只是在
Object.getPrototypeOf(foo)
对象上创建一个
bar
属性

该语句所指的是为
\uuuu proto\uuu
属性本身赋值:

function Employee() {}
var fred = new Employee();

// Assign a new object to __proto__
fred.__proto__ = Object.prototype;
// Or equally:
Object.setPrototypeOf(fred, Object.prototype);
位于的警告将更详细地介绍:

根据现代JavaScript引擎优化属性访问的本质,改变对象的[[Prototype]]是一个非常缓慢的操作

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

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

一些值得注意的引语:

  • 可写(writeable)proto(proto)是实现的一大难题(必须序列化到循环检查),它会造成各种类型的混淆危险

  • :

    允许脚本对几乎任何对象的原型进行变异,使得更难对脚本的行为进行推理,并使VM、JIT和分析实现更复杂、更麻烦。由于可变的_proto _uuu,类型推断出现了几个错误,并且由于这个特性(即“类型集包含可以为var/属性实现的所有可能的类型对象”和“JSFunctions具有同样是函数的类型”)而无法维护几个理想的不变量

  • :

    原型在创建后发生变异,其不稳定的性能不稳定,以及对代理和[[SetInheritation]]的影响

  • :

    我不期望通过使proto不可覆盖而获得很大的性能提升。在非优化代码中,如果原型对象(而不是它们的标识)已更改,则必须检查原型链。在优化代码的情况下,如果有人向proto写入代码,您可以退回到非优化代码。所以它不会有太大的区别,至少在V8曲轴上是这样

  • 当您设置_proto _时,您不仅破坏了从Ion对该对象进行未来优化的任何机会,而且还迫使引擎爬行到所有其他类型推断(可能是关于函数返回值或属性值的信息)他们认为他们知道这个对象,并告诉他们也不要做很多假设,这涉及到进一步的去优化,可能会使现有的JIT代码失效。
    在执行过程中改变一个物体的原型实际上是一个讨厌的大锤,我们必须避免错误的唯一方法是安全地运行,但是安全是缓慢的。
不。两者都在做相同的事情(如
foo.\uuu proto\uuu==foo.prototype
),而且都很好。他们只是在
Object.getPrototypeOf(foo)
对象上创建一个
bar
属性

该语句所指的是为
\uuuu proto\uuu
属性本身赋值:

function Employee() {}
var fred = new Employee();

// Assign a new object to __proto__
fred.__proto__ = Object.prototype;
// Or equally:
Object.setPrototypeOf(fred, Object.prototype);
位于的警告将更详细地介绍:

根据现代JavaScript引擎优化属性访问的本质,改变对象的[[Prototype]]是一个非常缓慢的操作

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

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

一些值得注意的引语:

  • 可写的原版是一个巨大的pai
    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)
    
    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()