为什么改变对象的[[prototype]]会影响性能?
Why is mutating the [[prototype]] of an object bad for performance?
来自标准 setPrototypeOf
函数以及非标准__proto__
属性的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.__proto__.bar = bar;
坏?如果它很糟糕,Foo.prototype.bar = bar;
不也一样吗?
那么为什么会出现这个警告:它非常慢,并且不可避免地减慢了现代JavaScript实现中的后续执行。当然Foo.prototype.bar = bar;
没有那么糟糕。
Update也许突变意味着重赋。见接受的答案。
// This is bad: //foo.__proto__.bar = bar; // But this is okay Foo.prototype.bar = bar;
。两者都在做同样的事情(如foo.__proto__ === Foo.prototype
),两者都很好。他们只是在Object.getPrototypeOf(foo)
对象上创建了一个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()
创建一个具有不同原型链的新对象。
我找不到明确的引用,但如果我们考虑V8的隐藏类是如何实现的(以及最近的报道),我们可以看到这里可能发生的事情。当改变一个对象的原型链时,它的内部类型也会改变——它不会像添加属性那样简单地变成一个子类,而是完全被交换了。这意味着所有的属性查找优化都会被刷新,并且需要丢弃预编译的代码。或者它只是返回到未优化的代码。
一些值得注意的引用:
Brendan Eich(你知道他)说
Writable _proto_实现起来非常痛苦(必须序列化到循环检查),它会产生各种类型混淆的危险。
Brian Hackett (Mozilla)说:
允许脚本改变几乎任何对象的原型,这使得对脚本的行为进行推理变得更加困难,并使VM、JIT和分析实现变得更加复杂和更容易出错。由于可变的_原型_,类型推断已经有了几个bug,并且由于这个特性(例如:"类型集包含所有可能的类型对象,可以实现一个var/property"answers"JSFunctions的类型也是函数")。
Jeff Walden说:
原型创建后的突变,其不稳定的性能不稳定,以及对代理和[[SetInheritance]]的影响
Erik Corry (Google)说:
我不期望从使proto不可重写中获得很大的性能收益。在非优化代码中,你必须检查原型链,以防原型对象(而不是它们的身份)被改变。在优化代码的情况下,如果有人向proto写入代码,您可以退回到非优化代码。所以它不会有太大的区别,至少在v8 -曲轴上。
Eric Faust (Mozilla) said
当您设置_proto_时,您不仅破坏了Ion将来对该对象进行优化的任何机会,而且还迫使引擎爬遍所有其他类型推断(关于函数返回值或属性值的信息),这些推断认为它们知道该对象,并告诉它们不要做太多假设,这涉及到进一步的反优化,可能还会使现有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
,目前没有其他方法可以做到这一点。几乎没有人使用不能继承父构造函数的构造函数,Object.create
无法提供服务。
另外,这是两个Object.create
调用,目前在V8(浏览器和Node)中非常缓慢,这使得__proto__
成为更可行的选择
是的。prototype=同样糟糕,因此措辞"无论如何完成"。Prototype是用于在类级别扩展功能的伪对象。它的动态特性减慢了脚本的执行速度。另一方面,在实例级添加函数所带来的开销要小得多。
下面是使用节点v6.11.1
的基准测试
NormalClass:正常类,原型未编辑
PrototypeEdited:已编辑原型的类(添加了test()
函数)
prototyperefence :一个增加了原型函数test()
的类,它引用一个外部变量
结果:
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()
- NodeJ中的注释会影响性能吗
- 设置1400个Raphael.js对象的不透明度动画会影响动画性能
- javascript getAttribute是否会影响性能或触发布局
- Coffeescript 隐式返回对性能和副作用的影响
- 行之间有空格会影响 JavaScript 代码的性能吗?
- 角度 1 路绑定不影响性能
- 数字精度如何影响 JavaScript 的性能,或者会影响性能
- ng-csp指令的性能影响
- 解除所有元素上的所有 jQuery 事件的性能影响
- Meteor 中的客户端渲染和光纤的性能影响
- 在计时器中持续访问 Cookie 的性能影响
- Node.js中未使用需求的性能影响
- Javascript-命名空间嵌套是否存在任何硬性限制(或性能影响)
- 对加载相关下拉列表的性能影响,最多约30000条记录
- 在一个应用程序中使用多个角度应用程序时的性能影响
- 1)对函数内部函数的性能影响2)多个条件下的顺序或流程
- KineticJS - KineticJS . node .listening()的性能影响
- 在Redux中切换到Immutable.js.性能影响是什么?对组件的影响是什么? '语法
- JavaScript base62编码的性能影响
- JavaScript对象文字表示法与普通函数及其性能影响