热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

拿下JavaScript引擎的基本原理

javascript栏目今天介绍JavaScript引擎的基本原理。
作为Javascript栏目开发人员,深入了解 Javascript 引擎的工作原理有助于你了解自己代码的性能特征。这篇文章对所有 Javascript 引擎中常见的一些关键基础知识进行了介绍,不仅仅局限于 V8 引擎。

Javascript 引擎的工作流程 (pipeline)

这一切都要从你写的 Javascript 代码开始。Javascript 引擎解析源代码并将其转换为抽象语法树(AST)。基于 AST,解释器便可以开始工作并生成字节码。就在此时,引擎开始真正地运行 Javascript 代码。除了 [[Value]] 本身,规范还定义了这些属性:

  • [[Writable]] 决定该属性是否能被重新赋值,
  • [[Enumerable]] 决定属性是否出现在 for in 循环中,
  • [[Configurable]] 决定属性是否能被删除。

[[双方括号]] 的符号表示看上去有些特别,但这正是规范定义不能直接暴露给 Javascript 的属性的表示方法。在 Javascript 中你仍然可以通过 Object.getOwnPropertyDescriptor API 获得指定对象的属性值:

const object = { foo: 42 };Object.getOwnPropertyDescriptor(object, 'foo');// → { value: 42, writable: true, enumerable: true, configurable: true } 

这就是 Javascript 定义对象的方式,那么数组呢?

你可以把数组看成是一个特殊的对象,其中的一个区别就是数组会对数组索引进行特殊的处理。这里的数组索引是 ECMAScript 规范中的一个特殊术语。在 Javascript 中限制数组最多有 2³²−1个元素,数组索引是在该范围内的任何有效索引,即 0 到 2³²−2 的任何整数。

另一个区别是数组还有一个特殊的 length 属性。

const array = ['a', 'b'];
array.length; // → 2array[2] = 'c';
array.length; // → 3 

在该例中,数组被创建时 length 为 2。当我们给索引为 2 的位置分配另一个元素时,length 自动更新了。

Javascript 定义数组的方式和对象类似。例如,所有的键值, 包括数组的索引, 都明确地表示为字符串。数组中的第一个元素,就是存储在键值 '0' 下。“length” 属性是另一个不可枚举且不可配置的属性。 当一个元素被添加到数组中时, Javascript 会自动更新 “length“ 属性的 [[value]] 属性。

优化属性访问

知道了对象在 Javascript 中是如何定义的, 那么就让我们来深入地了解一下 Javascript 引擎是如何高效地使用对象的。 总体来说,访问属性是至今为止 Javascript 程序中最常见的操作。因此,Javascript 引擎是否能快速地访问属性是至关重要的。

Shapes

在 Javascript 程序中,多个对象有相同的键值属性是非常常见的。可以说,这些对象有相同的 shape。

const object1 = { x: 1, y: 2 };const object2 = { x: 3, y: 4 };// object1 and object2 have the same shape. 

访问拥有相同 shape 的对象的相同属性也是非常常见的:

function logX(object) {    console.log(object.x);
}const object1 = { x: 1, y: 2 };const object2 = { x: 3, y: 4 };

logX(object1);
logX(object2); 

考虑到这一点,Javascript 引擎可以基于对象的 shape 来优化对象的属性访问。下面我们就来介绍其原理。

假设我们有一个具有属性 x 和 y 的对象,它使用我们前面讨论过的字典数据结构:它包含字符串形式的键,这些键指向它们各自的属性值。当我们有多个对象时,好处就显而易见了。不管有多少个对象,只要它们有相同的 shape,我们只需要存储 shape 和属性信息一次!

所有的 Javascript 引擎都使用了 shapes 作为优化,但称呼各有不同:

  • 学术论文称它们为 Hidden Classes(容易与 Javascript 中的 class 混淆)
  • V8 称它们为 Maps (容易与 Javascript 中的 Map 混淆)
  • Chakra 称它们为 Types (容易与 Javascript 中的动态类型以及 typeof 混淆)
  • JavascriptCore 称它们为 Structures
  • SpiderMonkey 称它们为 Shapes

本文中,我们将继续使用术语 shapes.

转换链和树

如果你有一个具有特定 shape 的对象,但你又向它添加了一个属性,此时会发生什么? Javascript 引擎是如何找到这个新 shape 的?

const object = {};
object.x = 5;
object.y = 6; 

这些 shapes 在 Javascript 引擎中形成所谓的转换链(transition chains)。下面是一个例子:

如果你在 Javascript 代码中写 o.x,Javascript 引擎会沿着转换链去查找属性 "x",直到找到引入属性 "x" 的 Shape。

但是如果没有办法创建一个转换链会怎么样呢?例如,如果有两个空对象,并且你为每个对象添加了不同的属性,该怎么办?

const object1 = {};
object1.x = 5;const object2 = {};
object2.y = 6; 

在这种情况下,我们必须进行分支操作,最终我们会得到一个转换树而不是转换链。

这里,我们创建了一个空对象 a,然后给它添加了一个属性 ‘x’。最终,我们得到了一个包含唯一值的 JSObject 和两个 Shape :空 shape 以及只包含属性 x 的 shape。

第二个例子也是从一个空对象 b 开始的,但是我们给它添加了一个不同的属性 ‘y’。最终,我们得到了两个 shape 链,总共 3 个 shape。

这是否意味着我们总是需要从空 shape 开始呢? 不一定。引擎对已含有属性的对象字面量会进行一些优化。比方说,我们要么从空对象字面量开始添加 x 属性,要么有一个已经包含属性 x 的对象字面量:

const object1 = {};
object1.x = 5;const object2 = { x: 6 }; 

在第一个例子中,我们从空 shape 开始,然后转到包含 x 的shape,这正如我们之前所见那样。

在 object2 的例子中,直接在一开始就生成含有 x 属性的对象,而不是生成一个空对象是有意义的。

包含属性 ‘x’ 的对象字面量从含有 ‘x’ 的 shape 开始,有效地跳过了空 shape。V8 和 SpiderMonkey (至少)正是这么做的。这种优化缩短了转换链并且使从字面量构建对象更加高效。

下面是一个包含属性 ‘x'、'y' 和 'z' 的 3D 点对象的示例。

const point = {};
point.x = 4;
point.y = 5;
point.z = 6; 

正如我们之前所了解的, 这会在内存中创建一个有3个 shape 的对象(不算空 shape 的话)。 当访问该对象的属性 ‘x’ 的时候,比如, 你在程序里写 point.x,Javascript 引擎需要循着链接列表寻找:它会从底部的 shape 开始,一层层向上寻找,直到找到顶部包含 ‘x’ 的 shape。

现在我们又回到字典查找了我们添加 shape 就是为了对此进行优化!那我们为什么要去纠结 shape 呢? 原因是 shape 启用了另一种称为 Inline Caches 的优化。

Inline Caches (ICs)

shapes 背后的主要动机是 Inline Caches 或 ICs 的概念。ICs 是让 Javascript 快速运行的关键要素!Javascript 引擎使用 ICs 来存储查找到对象属性的位置信息,以减少昂贵的查找次数。

这里有一个函数 getX,该函数接收一个对象并从中加载属性 x:

function getX(o) {    return o.x;
} 

如果我们在 JSC 中运行该函数,它会产生以下字节码:

对于后续运行,IC 只需要比较 shape,如果 shape 与之前相同,只需从存储的偏移量加载值。具体来说,如果 Javascript 引擎看到对象的 shape 是 IC 以前记录过的,那么它根本不需要接触属性信息,相反,可以完全跳过昂贵的属性信息查找过程。这要比每次都查找属性快得多。

高效存储数组

对于数组,存储数组索引属性是很常见的。这些属性的值称为数组元素。为每个数组中的每个数组元素存储属性特性是非常浪费内存的。相反,默认情况下,数组索引属性是可写的、可枚举的和可配置的,Javascript 引擎基于这一点将数组元素与其他命名属性分开存储。

思考下面的数组:

const array = [    '#jsconfeu',
]; 

引擎存储了数组长度(1),并指向包含偏移量和 'length' 属性特性的 shape。

每个数组都有一个单独的元素备份存储区,包含所有数组索引的属性值。Javascript 引擎不必为数组元素存储任何属性特性,因为它们通常都是可写的、可枚举的和可配置的。

那么,在非通常情况下会怎么样呢?如果更改了数组元素的属性特性,该怎么办?

// Please don’t ever do this!const array = Object.defineProperty(
    [],    '0',
    {        
        value: 'Oh noes!!1',        
        writable: false,        
        enumerable: false,        
        configurable: false,
    }); 

上面的代码片段定义了名为 “0” 的属性(恰好是数组索引),但将其特性设置为非默认值。

在这种边缘情况下,Javascript 引擎将整个元素备份存储区表示成一个字典,该字典将数组索引映射到属性特性。

即使只有一个数组元素具有非默认特性,整个数组的备份存储区也会进入这种缓慢而低效的模式。避免对数组索引使用Object.defineProperty!

建议

我们已经了解了 Javascript 引擎如何存储对象和数组,以及 shape 和 ICs 如何优化对它们的常见操作。基于这些知识,我们确定了一些可以帮助提高性能的实用的 Javascript 编码技巧:

  • 始终以相同的方式初始化对象,这样它们就不会有不同的 shape。
  • 不要弄乱数组元素的属性特性,这样可以有效地存储和操作它们。

相关免费学习推荐:Javascript(视频)

以上就是拿下Javascript引擎的基本原理的详细内容,更多请关注 第一PHP社区 其它相关文章!


推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文描述了作者第一次参加比赛的经历和感受。作者是小学六年级时参加比赛的唯一选手,感到有些紧张。在比赛期间,作者与学长学姐一起用餐,在比赛题目中遇到了一些困难,但最终成功解决。作者还尝试了一款游戏,在回程的路上感到晕车。最终,作者以110分的成绩取得了省一会的资格,并坚定了继续学习的决心。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 关羽败走麦城时路过马超封地 马超为何没有出手救人
    对当年关羽败走麦城,恰好路过马超的封地,为啥马超不救他?很感兴趣的小伙伴们,趣历史小编带来详细的文章供大家参考。说到英雄好汉,便要提到一本名著了,没错,那就是《三国演义》。书中虽 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 橱窗设计的表现手法及其应用
    本文介绍了橱窗设计的表现手法,包括直接展示、寓意与联想、夸张与幽默等。通过对商品的折、拉、叠、挂、堆等陈列技巧,橱窗设计能够充分展现商品的形态、质地、色彩、样式等特性。同时,寓意与联想可以通过象形形式或抽象几何道具来唤起消费者的联想与共鸣,创造出强烈的时代气息和视觉空间。合理的夸张和贴切的幽默能够明显夸大商品的美的因素,给人以新颖奇特的心理感受,引起人们的笑声和思考。通过这些表现手法,橱窗设计能够有效地传达商品的个性内涵,吸引消费者的注意力。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • faceu激萌变老特效的使用方法详解
    本文介绍了faceu激萌变老特效的使用方法,包括打开faceu激萌app、点击贴纸、选择热门贴纸中的变老特效,然后对准人脸进行拍摄,即可给照片添加变老特效。操作简单,适合新用户使用。 ... [详细]
author-avatar
kikokikolove
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有