博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在 V8 引擎中设置原型(prototypes)
阅读量:6913 次
发布时间:2019-06-27

本文共 2835 字,大约阅读时间需要 9 分钟。

  • 原文地址:
  • 原文作者:
  • 译文出自:
  • 本文永久链接:
  • 译者:
  • 校对者:

在 V8 引擎中设置原型(prototypes)

原型(比如 func.prototype )是用来模拟类的实现。它们通常包含类的所有方法,它们的 __proto__ 就是“父类(superclass)”,它们设置好后就不会修改了。

原型在设置时的性能表现对于应用程序的启动时间至关重要,因为此时通常要建立起整个类的层次结构。

转换对象形态(Transitioning object shapes)

对象被编码的主要方式是将隐藏类(描述)对象(内容)分隔开。当一个对象被实例化,和之前来自同一个构造函数的对象使用相同的初始化隐藏类。当属性被添加,对象从一个隐藏类切换到另一个隐藏类,通常是在所谓的“转换树(transition tree)”中重复之前的转换。举个例子,比如我们有以下的构造函数:

function C() {  this.a = 1;  this.b = 2;}复制代码

如果我们实例化一个对象 var o = new C(),它首先会使用一个没有任何属性的初始化隐藏类 M0。当 a 被添加,我们将从 M0 切换到一个新的隐藏类 M1,M1 描述属性 a。接着添加 b 的时候,我们再切换到另一个新的隐藏类来描述 ab

如果我们现在实例化第二个对象 var o2 = new C(),它将重复上面的转换。从 M0 开始,接着 M1,最后是 M2。ab 被添加完成。

这样做有三个重要的好处:

  1. 尽管创建第一个对象的开销是很大的,并且要求我们创建所有隐藏的类和转换,但是创建后续对象是非常快的。
  2. 结果对象比完整的字典要小。我们只需要在对象中存储值,而不需要存储关于属性的信息(比如名称)。
  3. 我们现在在内联缓存(inline cache)和优化代码时有一个对象形态可以使用,以后访问类似形态的对象就可以在同一位置找,方便快捷。

这样有利于频繁创建相似形态的对象。同样的事情也发生在对象字面量中:{a:1, b:2} 内部也会有隐藏类 M0,M1 和 M2。

网上有很多相关知识讲解,大家可以去看看 Lars Bak 的视频:

YouTube 视频见:

原型(Prototypes)就像特别的雪花

不同于常规构造函数实例化对象,原型是典型的不与其他对象分享形态的对象。这会带来三点变化:

  1. 通常来讲,没有对象能从缓存的转换(cached transitions)中受益,而且设置转换树(transition tree)的开销也是没有必要的。
  2. 创建所有转换隐藏类的内存开销是很大的。事实上,在改变这个之前,我们通常会看到为了一个简单的原型就要用上一大堆的隐藏类。
  3. 从一个原型中加载实际上并不像在原型链中使用那么常见。如果我们通过原型链从一个原型对象中加载,我们将不会分发原型的隐藏类,以及需要用不同的方法检查它是否有效。

为了优化原型,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 在创建时就知道它是一个原型。

怎样设置原型(prototypes)?

如果你用下面的方式设置原型,我们在方法添加之前很容易就知道 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 = …复制代码

感谢 .


是一个翻译优质互联网技术文章的社区,文章来源为 上的英文分享文章。内容覆盖 、、、、、、、等领域,想要查看更多优质译文请持续关注 、、。

转载地址:http://dxicl.baihongyu.com/

你可能感兴趣的文章
ppp链路的pap认证与chap认证的基本原理和配置
查看>>
[Java] 练习题004: 将一个正整数分解质因数
查看>>
速战速决---3小时快速搭建Exchange+SFB(2)
查看>>
关于VCPROJ文件的说明
查看>>
FreeBSD完全新手指南
查看>>
经典广域网技术详解
查看>>
升级Exchange Server 2010 到 SP3时报错
查看>>
Redis配置讲解
查看>>
每天看的资料总结
查看>>
【Python2】04、Python程序控制结构
查看>>
我的友情链接
查看>>
Aspect Oriented Programming杂谈
查看>>
拨开云计算迷雾,助力企业业务创新
查看>>
linux系统管理之单机系统管理 - 文件系统
查看>>
MongoDB的安装及PHP扩展安装
查看>>
寻找&删除大文件及大文件夹
查看>>
关于黑板的独特创意
查看>>
linux下构建Samba文件共享服务器
查看>>
jQuery easyUI页面布局
查看>>
[某鸥实训记][objective-c][第五天][个人笔记]
查看>>