- 原文地址:Setting up prototypes in V8
- 原文做者:Toon Verwaest
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:缪宇
- 校对者:zhmhhu 老教授
原型(好比 func.prototype
)是用来模拟类的实现。它们一般包含类的全部方法,它们的 __proto__
就是“父类(superclass)”,它们设置好后就不会修改了。前端
原型在设置时的性能表现对于应用程序的启动时间相当重要,由于此时一般要创建起整个类的层次结构。android
对象被编码的主要方式是将隐藏类(描述)和对象(内容)分隔开。当一个对象被实例化,和以前来自同一个构造函数的对象使用相同的初始化隐藏类。当属性被添加,对象从一个隐藏类切换到另外一个隐藏类,一般是在所谓的“转换树(transition tree)”中重复以前的转换。举个例子,好比咱们有如下的构造函数:ios
function C() {
this.a = 1;
this.b = 2;
}
复制代码
若是咱们实例化一个对象 var o = new C()
,它首先会使用一个没有任何属性的初始化隐藏类 M0。当 a
被添加,咱们将从 M0 切换到一个新的隐藏类 M1,M1 描述属性 a
。接着添加 b
的时候,咱们再切换到另外一个新的隐藏类来描述 a
和 b
。git
若是咱们如今实例化第二个对象 var o2 = new C()
,它将重复上面的转换。从 M0 开始,接着 M1,最后是 M2。a
和 b
被添加完成。github
这样作有三个重要的好处:后端
这样有利于频繁建立类似形态的对象。一样的事情也发生在对象字面量中:{a:1, b:2}
内部也会有隐藏类 M0,M1 和 M2。缓存
网上有不少相关知识讲解,你们能够去看看 Lars Bak 的视频:bash
YouTube 视频见:V8: an open source JavaScript engine函数
不一样于常规构造函数实例化对象,原型是典型的不与其余对象分享形态的对象。这会带来三点变化:post
为了优化原型,V8 对其形态的跟踪不一样于常规的转换对象,咱们不须要跟踪转换树(transition tree),而是将隐藏类调整为原型对象,让它保持高性能。举个例子,好比执行 delete object.property
会拖慢对象的性能,但若是是原型就不会出现这种状况。由于咱们老是会保持它们的可缓存性(有些问题咱们还在解决中)。
咱们也改变了原型的设置。原型包含了2个重要的阶段:设置和使用。原型在设置阶段被编译成字典对象(dictionary objects)。在那个状态下存储原型的速度很是快的,并且不须要进入 C++ 的运行时(跨边界的花销是很是巨大的)。与建立一个转换隐藏类来初始化对象相比,这是一个巨大的进步,由于前者必须进入C++ 运行时才行。
任何对原型的直接访问,或者经过原型链访问原型,都会将它切换成使用状态,这样确保了全部访问今后时开始是快速的。当处于使用状态,即便你删除属性,在删除以后咱们也会快速的切换回来。
function Foo() {}
// 如今 proto 对象是"设置"模式。
var proto = Foo.prototype;
proto.method1 = function() { ... }
proto.method2 = function() { ... }
var o = new Foo();
// 切换 proto 到"使用"模式。
o.method1();
// 也会切换 proto 到"使用"模式。
proto.method1.call(o);
复制代码
为了用上上面说的优化方法,咱们须要知道一个对象是否真的会被做为原型使用。因为 JavaScript 的特性,咱们很难在编译阶段分析你的代码。出于这个缘由,咱们甚至没有尝试在对象建立过程当中肯定什么东西最终会成为原型(固然,之后可能会发生变化)。一旦咱们看到一个对象赋值给一个原型,咱们将对它进行标记。举个例子来说:
var o = {x:1};
func.prototype = o;
复制代码
一开始咱们也不知道 o
用做原型,直到赋值给 func.prototype
。我像往常那样花费巨大的开销来建立对象。一旦像它那样被赋值,它就被标记成原型,进入设置阶段。当你使用它,就会进入使用阶段。
若是你像下面这样写,咱们会在属性添加前就知道 o
是一个原型。因而它将在添加属性前进入设置阶段,后面的代码执行就会快得多:
var o = {};
func.prototype = o;
o.x = 1;
复制代码
注意你也能够这样使用 var o = func.prototype
,由于很显然 func.prototype
在建立时就知道它是一个原型。
若是你用下面的方式设置原型,咱们在方法添加以前很容易就知道 func.prototype 就是一个原型:
// 若是默认的 Object.prototype 为 __proto__,则省略下面这行代码。
func.prototype = Object.create(…);
func.prototype.method1 = …
func.prototype.method2 = …
复制代码
虽然已经很不错了,但事实上咱们不得不为每一个方法都加载一次 func.prototype
。尽管最近咱们正在进一步优化 func.prototype
的加载,但这种加载是没必要要的,性能和内存的使用将比直接访问本地变量访问更糟糕。
简而言之,理想的原型设置方法以下:
var proto = func.prototype = Object.create(…);
proto.method1 = …
proto.method2 = …
复制代码
感谢 Benedikt Meurer.
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。