一、深入V8引擎原理
1.1 V8引擎执行代码的流程
- 官方V8引擎的定义
- V8是用C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等。
- 它实现ECMAScript和WebAssembly,并在Windows 7或更高版本,macOS 10.12+和使用x64,IA-32,ARM或MIPS处理器的Linux系统上运行。
- V8可以独立运行,也可以嵌入到任何C ++应用程序中
1.2 V8引擎其他内容补充
- 核心3个模块
- Parse模块 将JavaScript代码转成AST Tree
- Ignition :解释器 将ASTTree 转换为字节码(byte Code)
- 同时收集TurboFan 优化需要的信息
- TurboFan :编译器 将字节码编译为CPU可以直接执行的机器码(machine code)
- 如果某一个函数呗被多次调用 则会被标记为热点函数 会经过TurBoFan转换的优化的机器码 让CPU执行 提高代码性能
- 如果后续执行代码过程中 改函数调用时的参数类型发生了改变 则会逆向的转成字节码 让CPU执行
- 执行流程:
-
词法分析(scanner)
- 会将对应的每一行的代码的字节流分解成有意义的代码块 代码块被称为词法单元(token 进行记号化)
-
语法分析(parser)
- 将对应的tokens分析成一个元素逐级嵌套的树 这个树称之为 抽象语法树(Abstract Syntax Tree AST)
- 这里也有对应的 pre-parser
-
将AST 通过Ignition解释器转换成对应的字节码(ByteCode) 交给CPU执行 同时收集信息
- 将可优化的信息 通过TurBoFan编译器 编译成更好使用的机器码交给CPU执行
- 如果后续代码的参数类型发生改变 则会逆优化(Deoptimization)为字节码
-
二、执行上下文
2.1 初始化全局对象
-
js引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
- 该对象所有的作用域(scope)都可以访问
- 里面会包含Date、Array、String、Number、setTimeout、setInterval等等
- 其中还有一个window属性指向自己
2.2 执行上下文
-
js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈,执行的是全局的代码块:
- 全局的代码块为了执行会构建一个全局执行上下文Global Execution Context(GEC),GEC会 被放入到ECS中 执行
-
GEC被放入到ECS中里面包含两部分内容:
-
在代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值
- 这个过程也称之为变量的作用域提升(hoisting)
-
在代码执行中,对变量赋值,或者执行其他的函数
-
-
每一个执行上下文会关联一个VO(Variable Object,变量对象),变量和函数声明会被添加到这个VO对象中
- 当全局代码被执行的时候,VO就是GO对象
三、全局代码执行过程
var message = "Global Message"
function foo() {
var message = "Foo Message"
}
var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
- 代码执行前
- 代码执行过程
四、函数代码执行过程
-
在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且压入到EC Stack中。
-
每个执行上下文都会关联一个VO,当进入一个函数执行上下文时,会创建一个AO对象(Activation Object)
-
AO对象会使用
arguments作为初始化,并且初始值是传入的参数 -
AO对象会作为执行上下文的VO来存放变量的初始化
-
4.1 普通函数执行
var message = "Global Message"
function foo(num) {
var message = "Foo Message"
var age = 18
var height = 1.88
console.log("foo function")
}
foo(123)
var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
-
代码执行前
-
代码执行过程
4.2 函数多次执行
var message = "Global Message"
function foo(num) {
var message = "Foo Message"
var age = 18
var height = 1.88
console.log("foo function")
}
foo(123)
foo(321)
foo(111)
foo(222)
var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
-
执行foo(123)
-
执行foo(321)
- 会重新创建AO
- 执行完,EC弹出栈
4.3 函数相互调用
var message = "Global Message"
var obj = {
name: "zhangsan"
}
function test() {
}
function bar() {
console.log("bar function")
test()
test()
var address = "bar"
}
function foo(num) {
var message = "Foo Message"
bar()
var age = 18
var height = 1.88
console.log("foo function")
}
foo(123)
var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
-
全局代码执行,foo和bar函数执行
-
bar函数执行完毕
-
foo函数执行完毕
五、作用域作用域链
5.1 概念
-
当进入到一个执行上下文时,执行上下文也会关联一个作用域链(Scope Chain)
-
作用域链是一个对象列表,用于变量标识符的求值
-
当进入一个执行上下文时,这个作用域链被创建,并且根据代码类型,添加一系列的对象
-
5.2 函数代码查找变量
-
注意:函数在创建的那一刻就已经确定了自己的作用域链,跟调用位置无关
var message = "Global Message" function foo() { var name = "foo" setTimeout(function() { console.log(name, message) }, 3000) } foo() // foo Global Message -
普通函数
var message = "Global Message" function foo() { console.log(message) } foo() var obj = { name: "obj", bar: function() { var message = "bar message" foo() } } obj.bar() // Global Message-
查找过程
-
-
函数多层嵌套
var message = "global message" function foo() { var name = "foo" function bar() { console.log(name) } return bar } var bar = foo() bar()-
查找过程
-
5.4 面试题
// 1.面试题一:
// var n = 100
// function foo() {
// n = 200
// }
// foo()
// console.log(n) // 200
// 2.面试题二:
// var n = 100
// function foo() {
// console.log(n) // undefined
// var n = 200
// console.log(n) // 200
// }
// foo()
// 3.面试题三:
// var n = 100
// function foo1() {
// console.log(n) // 100
// }
// function foo2() {
// var n = 200
// console.log(n) // 200
// foo1()
// }
// foo2()
// 4.面试题四:
// var n = 100
// function foo() {
// console.log(n) // undefined
// return
// var n = 200
// }
// foo()
// 5.在开发中可能会出现这样错误的写法
// function foo() {
// message = "Hello World"
// }
// foo()
// console.log(message)
// 6.面试题五:
function foo() {
var a = b = 100
// 1. var a 定义
// 2. a = b 赋值(=赋值从右往左)
// 3. b = 100
}
foo()
// console.log(a) // a is not defined
console.log(b)