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是一个伪对象,用于在类级别扩展功能。它的动态特性会减慢脚本的执行速度。另一方面,在实例级别添加一个函数会带来更少的开销

不。两者都在做相同的事情(如
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代码失效。
    在执行过程中改变一个物体的原型实际上是一个讨厌的大锤,我们必须避免错误的唯一方法是安全地运行,但是安全是缓慢的。

\uuuu proto\uuuu
/
setPrototypeOf
与分配给对象原型不同。例如,当您有一个分配了成员的函数/对象时:

函数构造函数(){
if(!(此构造函数实例)){
返回新构造函数();
} 
}
Constructor.data=1;
Constructor.staticMember=函数(){
返回此.data;
}
Constructor.prototype.instanceMember=函数(){
返回此.constructor.data;
}
Constructor.prototype.Constructor=构造函数;
//通过执行以下操作,您所做的几乎与分配给相同
//但实际上不一样:P
var newObj=Object.create(构造函数);//但是newObj现在是一个对象而不是一个对象
//功能就像!!!建造师!!!
//(typeof newObj==='object'!==typeof构造函数==='function'),而您
//失去实例化它的能力,“newnewobj”返回的不是构造函数,
//你有。原型,但不能使用它。
newObj=Object.create(Constructor.prototype);
//现在您可以访问newObj.instanceMember了
//但staticMember不可用。构造函数的newObj instanceof为true
//我们可以使用类似于原始构造函数的函数来保留
//功能,如自调用它newObj(),访问静态
//成员等,这在Object.create中是不可能的
var newObj=函数(){
如果(!(newObj的这个实例)){
返回新的newObj();
}
}; 
newObj.\uuuuu proto\uuuuuj=构造函数;
newObj.prototype.\uuuu proto\uuuu=Constructor.prototype;
newObj.data=2;
(新的newObj()).instanceMember()//2.
newObj().instanceMember()/
function Employee() {}
var fred = new Employee();

// Assign a new object to __proto__
fred.__proto__ = Object.prototype;
// Or equally:
Object.setPrototypeOf(fred, Object.prototype);
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()