第一篇
这是《图解 Google V8》第一篇/共三篇:设计思想篇
这个专栏的优点是:写的通俗易懂,没有涉及 V8 源码部分,对于前端还是比较友好的,学完之后能够知道写下一段 js 代码后,V8 背后都做了哪些事情
这个专栏的不足之处是:没有对技术进行深入讲解,只讲了这个技术是用来解决什么问题的,以及它是怎么工作的
所以这个专栏比较时候对 V8 还不了解的同学去学习,增加自己的知识面
下面是我自己学习每一章的总结,主要记录我在这章中学到内容,并不是对这章完整的总结
我会分为三篇来写,这是第一篇:设计思想篇
如何学习谷歌高性能 JavaScript 引擎 V8?
V8 主要涉及三个技术:编译流水线、事件循环系统、垃圾回收机制
V8 执行 JavaScript 完整流程称为:编译流水线

编译流水线涉及到的技术有:
- JIT
- 惰性解析
- 隐藏类(
Hide Class)
- 将动态类型转为静态类型,消除动态类型执行速度慢的问题
- 内联缓存
- 事件循环系统
JavaScript 中的难点:异步编程
- 调度排队任务,让
JavaScript 有序的执行
- 垃圾回收机制
01 | V8 是如何执行一段 JavaScript 代码的?
- 准备基础环境:
- 全局执行上下文:全局作用、全局变量、内置函数
- 初始化内存中的堆和栈结构
- 初始化消息循环系统:消息驱动器和消息队列
- 结构化
JavaScript 源代码
- 生成字节码:字节码是介于
AST 和机器码的中间代码
- 解释器可以直接执行
- 编译器需要将其编译为二进制的机器码再执行
- 解释器和监控机器人
- 解释器:按照顺序执行字节码,并输出执行结果
- 监控机器人:如果发现某段代码被重复多次执行,将其标记为热点代码
- 优化热点代码
- 优化编译器将热点代码编译为机器码
- 对编译后的机器码进行优化
- 再次执行到这段代码时,将优先执行优化后的代码
- 反优化
JavaScript 对象在运行时可能会被改变,这段优化后的热点代码就失效了
- 进行反优化操作,给到解释器解释执行
02 | 函数即对象:一篇文章彻底搞懂 JavaScript 的函数特点
V8 内部为函数对象添加了两个隐藏属性:name,code:
name 为函数名
code 为函数代码,以字符串的形式存储在内存中
当执行到一个函数调用语句时,V8 从函数对象中取出 code 属性值,然后解释执行这段函数代码
什么是闭包:将外部变量和函数绑定起来的技术
参考资料:
- The story of a V8 performance cliff in React
03 | 快属性和慢属性:V8 是怎样提升对象属性访问速度的?
V8 在实现对象存储时,没有完全采用字典的存储方式,因为字典是非线性的数据结构,查询效率会低于线性的数据结构
常规属性和索引属性
- 索引属性(
elements):数字属性按照索引值的大小升序排列
- 常规属性(
properties):字符串根据创建时的顺序升序排列
它们都是线性数据结构,分别为 elements 对象和 properties 对象
执行一次查询:先从 elements 对象中按照顺序读出所有元素,然后再从 properties 对象中读取所有的元素
快属性和慢属性
在访问一个属性时,比如:foo.a,V8 先查找出 properties,然后在从 properties 中查找出 a 属性
V8 为了简化这一步操作,把部分 properties 存储到对象本身,默认是 10 个,这个被称为对象内属性
线性数据结构通常被称为快属性
线性数据结构进行大量数据的添加和删除,执行效率是非常低的,所以 V8 会采用慢属性策略
慢属性的对象内部有独立的非线性数据结构(字典)
参考资料:
- V8 是怎么跑起来的 —— V8 中的对象表示
- Fast properties in V8
04 | 函数表达式:涉及大量概念,函数表达式到底该怎么学?
变量提升
在 js 中有函数声明的方式有两种:
- 函数声明
function foo() {
console.log("foo");
}
- 函数表达式
var foo = function () {
console.log("foo");
};
在编译阶段 V8 解析到函数声明和函数表达式(变量声明)时:
- 函数声明,将其转换为内存中的函数对象,并放到作用域中
- 变量声明,将其值设置为
undefined,并当道作用域中
因为在编译阶段,是不会执行表达式的,只会分析变量的定义、函数的声明
所以如果在声明前调用 foo 函数:
在编译阶段将所有的变量提升到作用域的过程称为变量提升
立即执行函数
js 的圆括号 () 可以在中间放一个表达式
中间如果是一个函数声明,V8 就会把 (function(){}) 看成是函数表达式,执行它会返回一个函数对象
如果在函数表达式后面加上(),就被称为立即调用函数表达式
因为函数立即表达式也是表达式,所以不会创建函数对象,就不会污染环境
05 |原型链:V8 是如何实现对象继承的?
- 作用域链是沿着函数的作用域一级一级来查找变量的
- 原型链是沿着对象的原型一级一级来查找属性的
js 中实现继承,是将 __proto__ 指向对象,但是不推荐使用,主要原因是:
- 这是隐藏属性,不是标准定义的
- 使用该属性会造成严重的性能问题
继承
- 用构造函数实现继承:
function DogFactory(color) {
this.color = color;
}
DogFactory.prototype.type = "dog";
const dog = new DogFactory("Black");
dog.hasOwnProperty("type"); // false
ES6 之后可以通过 Object.create 实现继承
const animalType = { type: "dog" };
const dog = Object.create(animalType);
dog.hasOwnProperty("type"); // false
new 背后做了这些事情
- 帮你在内部创建一个临时对象
- 将临时对象的
__proto__ 设置为构造函数的原型,构造函数的原型统一叫做 prototype
return 临时对象
function NEW(fn) {
return function () {
var o = { __proto__: fn.prototype };
fn.apply(o, arguments);
return o;
};
}
__proto__、prototype、constructor 区别
prototype 是函数的独有的;__proto__ 和 constructor 是对象独有的
由于函数也是对象,所以函数也有 __proto__ 和 constructor
constructor 是函数;prototype 和 __proto__ 是对象
typeof Object.__proto__; // "object"
typeof Object.prototype; // "object"
typeof Object.constructor; // "function"
let obj = new Object();
obj.__proto__ === Object.prototype;
obj.constructor === Object;
obj 是 Object 的实例,所以 obj.constructor === Object
obj 的是对象,Object 是函数,所以 obj.__proto__ === Object.prototype
参考资料:
- 用自己的方式(图)理解
constructor、prototype、__proto__ 和原型链
- 面试官问:JS 的继承
06 |作用域链:V8 是如何查找变量的?
全局作用域是在 V8 启动过程中就创建了,且一直保存在内存中不会被销毁的,直至 V8 退出
而函数作用域是在执行该函数时创建的,当函数执行结束之后,函数作用域就随之被销毁掉了
因为 JavaScript 是基于词法作用域的,词法作用域就是指,查找作用域的顺序是按照函数定义时的位置来决定的。
词法作用域是静态作用域,根据函数在代码中的位置来确定的,作用域是在声明函数时就确定好了
动态作用域链是基于调用栈的,不是基于函数定义的位置的,可以认为 this 是用来弥补 JavaScript 没有动态作用域特性的
07 |类型转换:V8 是怎么实现 1+“2”的?
V8 会提供了一个 ToPrimitive 方法,其作用是将 a 和 b 转换为原生数据类型
- 先检测该对象中是否存在
valueOf 方法,如果有并返回了原始类型,那么就使用该值进行强制类型转换
- 如果
valueOf 没有返回原始类型,那么就使用 toString 方法的返回值
- 如果
valueOf 和 toString 两个方法都不返回基本类型值,便会触发一个 TypeError 的错误。
第一篇
这是《图解 Google V8》第一篇/共三篇:设计思想篇
这个专栏的优点是:写的通俗易懂,没有涉及
V8源码部分,对于前端还是比较友好的,学完之后能够知道写下一段js代码后,V8背后都做了哪些事情这个专栏的不足之处是:没有对技术进行深入讲解,只讲了这个技术是用来解决什么问题的,以及它是怎么工作的
所以这个专栏比较时候对
V8还不了解的同学去学习,增加自己的知识面下面是我自己学习每一章的总结,主要记录我在这章中学到内容,并不是对这章完整的总结
我会分为三篇来写,这是第一篇:设计思想篇
如何学习谷歌高性能 JavaScript 引擎 V8?
V8主要涉及三个技术:编译流水线、事件循环系统、垃圾回收机制V8执行JavaScript完整流程称为:编译流水线编译流水线涉及到的技术有:
V8混合编译执行和解释执行Hide Class)JavaScript中的难点:异步编程JavaScript有序的执行01 | V8 是如何执行一段 JavaScript 代码的?
JavaScript源代码AST)AST和机器码的中间代码JavaScript对象在运行时可能会被改变,这段优化后的热点代码就失效了02 | 函数即对象:一篇文章彻底搞懂 JavaScript 的函数特点
V8内部为函数对象添加了两个隐藏属性:name,code:name为函数名name为anonymouscode为函数代码,以字符串的形式存储在内存中当执行到一个函数调用语句时,
V8从函数对象中取出code属性值,然后解释执行这段函数代码什么是闭包:将外部变量和函数绑定起来的技术
参考资料:
03 | 快属性和慢属性:V8 是怎样提升对象属性访问速度的?
V8在实现对象存储时,没有完全采用字典的存储方式,因为字典是非线性的数据结构,查询效率会低于线性的数据结构常规属性和索引属性
elements):数字属性按照索引值的大小升序排列properties):字符串根据创建时的顺序升序排列它们都是线性数据结构,分别为
elements对象和properties对象执行一次查询:先从
elements对象中按照顺序读出所有元素,然后再从properties对象中读取所有的元素快属性和慢属性
在访问一个属性时,比如:
foo.a,V8先查找出properties,然后在从properties中查找出a属性V8为了简化这一步操作,把部分properties存储到对象本身,默认是10个,这个被称为对象内属性线性数据结构通常被称为快属性
线性数据结构进行大量数据的添加和删除,执行效率是非常低的,所以
V8会采用慢属性策略慢属性的对象内部有独立的非线性数据结构(字典)
参考资料:
04 | 函数表达式:涉及大量概念,函数表达式到底该怎么学?
变量提升
在
js中有函数声明的方式有两种:在编译阶段
V8解析到函数声明和函数表达式(变量声明)时:undefined,并当道作用域中因为在编译阶段,是不会执行表达式的,只会分析变量的定义、函数的声明
所以如果在声明前调用
foo函数:在编译阶段将所有的变量提升到作用域的过程称为变量提升
立即执行函数
js的圆括号()可以在中间放一个表达式中间如果是一个函数声明,
V8就会把(function(){})看成是函数表达式,执行它会返回一个函数对象如果在函数表达式后面加上
(),就被称为立即调用函数表达式因为函数立即表达式也是表达式,所以不会创建函数对象,就不会污染环境
05 |原型链:V8 是如何实现对象继承的?
js中实现继承,是将__proto__指向对象,但是不推荐使用,主要原因是:继承
ES6之后可以通过Object.create实现继承new 背后做了这些事情
__proto__设置为构造函数的原型,构造函数的原型统一叫做prototypereturn临时对象__proto__、prototype、constructor区别prototype是函数的独有的;__proto__和constructor是对象独有的由于函数也是对象,所以函数也有
__proto__和constructorconstructor是函数;prototype和__proto__是对象obj是Object的实例,所以obj.constructor === Objectobj的是对象,Object是函数,所以obj.__proto__ === Object.prototype参考资料:
constructor、prototype、__proto__和原型链06 |作用域链:V8 是如何查找变量的?
全局作用域是在
V8启动过程中就创建了,且一直保存在内存中不会被销毁的,直至V8退出而函数作用域是在执行该函数时创建的,当函数执行结束之后,函数作用域就随之被销毁掉了
因为
JavaScript是基于词法作用域的,词法作用域就是指,查找作用域的顺序是按照函数定义时的位置来决定的。词法作用域是静态作用域,根据函数在代码中的位置来确定的,作用域是在声明函数时就确定好了
动态作用域链是基于调用栈的,不是基于函数定义的位置的,可以认为
this是用来弥补JavaScript没有动态作用域特性的07 |类型转换:V8 是怎么实现 1+“2”的?
V8会提供了一个ToPrimitive方法,其作用是将a和b转换为原生数据类型valueOf方法,如果有并返回了原始类型,那么就使用该值进行强制类型转换valueOf没有返回原始类型,那么就使用toString方法的返回值valueOf和toString两个方法都不返回基本类型值,便会触发一个TypeError的错误。