深入浅出 JavaScript:v8引擎的预编译与执行

140 阅读4分钟

一、JavaScript引擎的“两步走”策略

v8引擎读取 JavaScript 代码后,遵循先编译,再执行的策略。

  1. 首先对全局代码进行编译,进行词法分析、语法分析等,将源代码转换成内部形式。随后执行全局代码,完成全局变量初始化、函数声明等操作。
  2. 全局代码执行完毕后,开始编译函数内部代码,编译完成后再执行函数代码。

编译全局->执行全局->编译函数->执行函数”这种顺序能优化代码执行性能,确保代码按预期逻辑正确执行。

二、全局预编译三部曲

  1. 创建全局执行上下文对象(GO)
  2. 变量声明,将变量名作为上下文对象的属性名,值为 undefined
  3. 函数声明,函数名作为上下文对象的属性名,值为 函数体

三、函数体预编译四部曲

  1. 创建函数的执行上下文对象(AO)
  2. 形参变量声明,将新参和变量名作为上下文对象的属性名,值为 undefined
  3. 函数调用时,将实参的值传递给形参,完成赋值。将实参和形参统一
  4. 在函数体里面找函数声明,函数名作为上下文对象的属性名,值为 函数体

四、预编译实战解析

例 1:

function fn(a){
    console.log(a); // 输出:[Function: a]
    var a = 123
    console.log(a); // 输出:123
    function a(){} // 函数声明
    var b = function(){} // 函数表达式
    console.log(b); // [Function: b]
    function d(){}
    var d = a
    console.log(d); // 输出:123
}
fn(1);

全局预编译、执行和函数预编译过程:首先进行全局预编译,创建全局执行上下文,找变量声明(无),再找函数声明fn,值为函数体。第12行代码调用函数fn并入栈。此时创建函数fn的执行上下文,找形参a和变量声明b,da重复),值为undefined;将实参数和形参统一,即令a赋值为1;再找函数声明ad,值分别为Function: aFunction: d
注意同一作用域中,变量名和函数名若相同,则后者直接覆盖前者。

4b0b8310cf1a2677e40a8af18ba295b.png

执行过程:由函数预编译结果可知,第2行代码打印a值为Function: a;第3行a赋值为123,则第4行打印a123;第6行b值为函数体,打印为Function: b;第9行d赋值为a123,打印结果为123

c091b52aa65819a7b4c4e0876a65c33.png

例 2:

function foo(a, b) {
  console.log(a); // 输出:1
  c = 0 
  var c;
  a = 3
  b = 2
  console.log(b); // 输出:2
  function b() {}
  console.log(b); // 输出:2
}
foo(1)

全局预编译、执行和函数预编译过程:首先全局预编译,创建全局执行上下文,找变量声明(无),再找函数声明foo,值为函数体。第11行代码调用函数foo并入栈。此时创建函数foo的执行上下文,找形参ab和变量声明c,值为undefined;将实参数和形参统一,即令a赋值为1;再找函数声明b,值为Function: b

44dd2f7cb2e161028da5d3e89a4b054.png

函数执行过程:由函数预编译结果可知,第2行代码打印a值为1;第3行c赋值为0;第5行a赋值为3,第6行b赋值为2,故此时打印b值为2。

15dfcb0fafade8f28c98ad86a4c40ff.png

例 3:

var a = 1;
function fn(){
    var a = 2;
    function a(){}
    console.log(a); // 输出:2
}
fn();

全局预编译、执行和函数预编译过程:首先全局预编译,创建全局执行上下文,找变量声明a,值为undefined,再找函数声明fn,值为函数体。第7行代码调用函数fn并入栈。此时创建函数fn的执行上下文,找形参(无)和变量声明a,值为undefined;再找函数声明a,值为Function: a
函数执行过程:第3行a赋值为2,故此时打印a值为2。

3ebb7f993882230d3a9616ad706cba3.png

例 4:

global = 100
function fn(){
    console.log(global); // 输出:undefined
    global = 200
    console.log(global); // 输出:200
    var global = 300
}
fn()
var global

全局预编译、执行和函数预编译过程:首先全局预编译,创建全局执行上下文,找变量声明global,值为undefined,再找函数声明fn,值为函数体。执行全局,第1行代码global赋值为100。第8行代码调用函数fn并入栈。此时创建函数fn的执行上下文,找形参(无)和变量声明global,值为undefined
函数执行过程:由函数预编译结果可知,第3行代码打印global值为undefined;第4行global赋值为200,故此时打印值为200。第6行global赋值为300

5bd7638abf97f6c99c72752a23e7f73.png