本篇文章给大家从 JS 引擎方向来理解 JavaScript 代码, 从而进行优化. 有一定的参考价值, 有需要的朋友可以参考一下, 希望对大家有所帮助.
序言: 作为一名前端工程师, 对于 JavaScript 大家都不陌生, 这篇文章从更深层次的方向 --JS 引擎去理解 JavaScript 到底是怎么运行的, 从而进行优化.
JS Engine-- JS 引擎介绍
一, 基本介绍
JS 引擎是一个专门运行 JavaScript 的解释器(interpreter). 目前比较主流的 JS 引擎和介绍, 大家可以简单了解一下:
V8- 由谷歌使用 C++ 开源的 V8 引擎, 也是我们经常听到的一个引擎
Rhino-由 Mozilla Foundation 完全用 Java 管理的一个开源引擎
SpiderMonkey-第一代 JS 引擎, 曾经运行在 Netscape Navigator 浏览器中, 现在是 Firefox
JavaScriptCore- Safari 的开源 JS 引擎
KJS-KDE 引擎, 由 Harri Porten 开发
Chakra (JScript9)-Internet Explorer 引擎
Chakra (JavaScript)-Microsoft Edge 引擎
JerryScript- 轻量级的 JS 引擎, 主要用于 IOT
二, V8 引擎运行流程
现在由于 Node.JS 和谷歌浏览器的普及, 在此就主要介绍 V8 引擎的运行机制, 如果大家对其他引擎感兴趣, 可以自行查看, 基本上是大同小异的
我们可以看到, JS 引擎的处理过程是先解析转换为 AST 语法树, 然后由解释器主要做两件事, 第一个是转化为机器语言 bytecode, 第二个是交给编辑器 (optimizting compiler) 进行优化, 中间还有一个数据分析过程(profilling data), 主要目的是为了进行优化 JS 的运行, 优化完毕后的代码, 再转化为机器语言.
而 V8 引起的核心组件就分别是 Ignition 和 TurboFan 了
JS Code-- Talk is cheap
看到这, 你肯定会想, 我知道这有啥用, Talk is cheap, 有没有代码可以分析的. 那么大家请看以下示例代码, 通过分析代码后, 我再详细介绍其中的原理
示例代码一:
- // first case
- var a = {}
- var b = {}
- console.time()
- for (let k = 0; k < 9999999; k++) {
- a[k] = 0
- }
- for (let i = 0; i < 9999999; i++) {
- b[i] = 0
- }
- console.timeEnd()
- // second case
- var a = {}
- var b = {}
- console.time()
- for (let k = 0; k < 9999999; k++) {
- a[k] = 0
- }
- for (let i = 10000000; i < 19999999; i++) {
- b[i] = 0
- }
- console.timeEnd()
- // third case
- var a = {}
- var b = {}
- console.time()
- for (let k = 0; k < 9999999; k++) {
- a[k] = 0
- }
- for (let i = 9999999; i < 0; i--) {
- b[i] = 0
- }
- console.timeEnd()
看完以上代码, 内容很简单, 就是定义 object a 和 b 然后不断添加属性, 唯一区别的是, first case 是 a 和 b 重复添加相同的属性, second case 是 a 和 b 添加不同的属性, third case 是 a 和 b 重复添加相同的属性, 但是处理 b 的时候是相反顺序的.
那么问题来了: 三块代码, 运行速度有没有快慢之分, 分别又大不大呢? (不用去确认循环次数, 都是一样滴!)
答案来了: 用时时间大概是 3 (500ms)< 1 (1000ms) < 2 (2000ms), 几乎就是 2 倍的速度差了.
V8 Engine -- Hidden Class
我们知道, JS 是动态脚本语言, 什么意思呢, 就是你可以很简单的给 object 添加 / 删除属性, 或者改变其类型, 大部分的 JS 解释器 (interpreter) 使用字典结构, 在内存中存储变量属性值的地址, 这种方式比起 java 和 C#(非动态语言, 当然了 C# 的 dynamic 类型另当别论, 不在此赘述)要低效率的多, 因为 JS 的类型是可以随时转换的, 本来使用字典结构结合固定的类型进行判断, 可以较容易的找到变量属性值的位置, 而在 JS 中就难以实现了.
所以 V8 引擎就使用了一种高效率的方法叫 Hidden Class. 其他的引擎也有类似的方法, 有叫 Map 的, Structures 的, Hidden Class 的等等, 在这里我们用 Shape 来定义它, 这样方便大家理解.
当我们定义一个 object 的时候, 它会包含以下内容:
每个属性的意义可以见上表.
那么结合 Shape, 当定义一个 object 的时候, JS 引擎会创建一个 Shape (可以理解为连续的缓存 buffer), 它的位置 0 和 1 存储了 x 和 y 值, 如下图:
如果我定义了两个 object, 都是包含 x 和 y 的, 那么它就会共用一个 shape, 如下图:
所以到这里我们可以想象的到, 我们只要定义相同属性的 object, 那么都会共用一个 shape, 当我们要调用任意一个 shape 的属性值的时候, 都可以通过同一个 shape 的 offset 来获取到了.
那么, 当我们给 object 添加属性的时候呢? V8 引擎会根据类过渡 (class transition) 原理创建新的 shape 来标记位置, 如下图
也就是说, 我们创建了 3 个 shape, 通过过渡链 (transition chain) 来实现一个 object 的追溯.
看到这里, 大家肯定在想, 每多一个 shape 肯定就会多占用一块内存, 那么我为了优化的话, 尽量在初始化的时候把属性都定义好就能优化了, Bingo~ 我们可以参照下图来验证:
通过以上的原理解释, 相信大家肯定能够推算解释出, 我们的示例代码的执行速度的区别了.
First Case:
1. Shape (empty) for a 和 b
- Shape 1....9999999 for a
- Shape 1....9999999 for b
a 和 b 都共用了相同的 shape, 可以重复使用
Second Case:
1. Shape (empty) for a 和 b
- Shape 1....9999999 for a
- Shape 10000000....19999999 for b
一共定义了 1 到 19999999 的 shape, 那么 second case 肯定时间要比 first case 要花多一倍的时间了
Thrid Case:
1. Shape (empty) for a 和 b
- Shape 1....9999999 for a
- Shape 9999999 ....1 for b
源码: https://github.com/likeconan/Alipay_Wechat_Integration
本文转载自: https://blog.csdn.net/likeconan123/article/details/92653204
更多 web 前端开发 https://www.html.cn/ 知识, 请查阅 HTML 中文网 !!
来源: http://www.css88.com/web/javascript/17188.html