为什么改变对象的 \[\[prototype\]\] 会降低性能?

新手上路,请多包涵

来自 标准 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; 还不错。

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

原文由 basarat 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 490
2 个回答
> // 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 的隐藏类 是如何实现的(以及 最近的 文章),我们可以看到这里可能发生的事情。当改变一个对象的原型链时,它的内部类型也会改变——它不像添加属性时那样简单地变成一个子类,而是完全交换。这意味着所有属性查找优化都被刷新,并且需要丢弃预编译代码。或者它只是退回到未优化的代码。

一些值得注意的引述:

可写 _ proto _ 实现起来非常痛苦(必须序列化以进行循环检查)并且它会产生各种类型混淆的危险。

允许脚本改变几乎任何对象的原型都会使推断脚本的行为变得更加困难,并使 VM、JIT 和分析实现更加复杂和错误。由于 mutable _ proto _ 类型推断有几个错误,并且由于此功能而无法维护几个理想的不变量(即“类型集包含可以为 var/property 实现的所有可能的类型对象”和“JSFunctions 具有的类型也是功能’)。

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

我不希望通过使 proto 不可覆盖来获得巨大的性能提升。在未优化的代码中,您必须检查原型链,以防原型对象(不是它们的标识)已更改。在优化代码的情况下,如果有人写入 proto,您可以回退到未优化的代码。所以它不会有太大的不同,至少在 V8-Crankshaft 中是这样。

当您设置 _ proto _ 时,您不仅破坏了 Ion 对该对象的未来优化的任何机会,而且还迫使引擎爬行到所有其他类型推断(有关函数返回的信息值,或者属性值,可能)认为他们知道这个对象并告诉他们不要做很多假设,这涉及进一步的去优化和可能使现有的 jitcode 失效。

在执行过程中更改对象的原型确实是一把讨厌的大锤,我们避免犯错的唯一方法就是稳妥行事,但稳妥的做法很慢。

原文由 Bergi 发布,翻译遵循 CC BY-SA 4.0 许可协议

__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__ 成为一个更可行的选择

原文由 pocesar 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题