五分钟带你了解JavaScript的v8引擎的预编译机制,彻底弄清声明提升背后的逻辑

0 阅读3分钟

引言

你是否在深夜独自写js代码时因为js的v8引擎独特的声明提升机制而苦恼,捶胸顿足,明明变量写在后面,输出不会与其他底层语言一样报错,而是打印undefined,这就是你不深刻理解声明提升背后的预编译机制的后果,那么接下来,让我带你一文吃透js的预编译。

1.声明提升的“坑”

首先,让我们看一段代码,看看所谓的声明提升的“坑”

console.log(a);
var a = 10;

会打印输出什么呢?没想到吧,输出undefined,完全偏离了人的先定义,后输出的正常思维,竟然没有报错,还输出undefined。其实这是v8独有声明提升机制等效以下代码

var a;
console.log(a);
a = 10;

但当代码稍微复杂一点时,我们就不能单单只靠死记硬背声明提升从而判断代码输出结果,这时,我们就要引入预编译机制,了解v8执行的底层逻辑,才能做到不踩“坑”

预编译机制的执行过程

预编译包括函数预编译和全局预编译,同时- 预编译发生在代码执行之前,但并不是“一次性编译完所有代码”,函数的预编译是在“函数被调用时”才触发的,全局预编译在代码加载时触发。

1.函数预编译

  1. 创建一个执行上下文 AO:{}
  2. 找形参和变量声明,将形参和变量名作为属性名,添加到 AO 中,值为 undefined

3.将形参和实参统一

  1. 在函数体中找函数声明,将函数名作为AO中属性名,函数体作为属性值,添加到 AO 中 那么接下来我们看一个例子具体理解
function fn(a) {
  console.log(a);
  var a = 123
  console.log(a);
  function a() {}
  var b = function() {}
  console.log(b);
  function c() {}
  var c = a
  console.log(c);
}
fn(1)

在函数调用前,v8会先进行预编译生成执行上下文AO

AO={
    a:undefined,1,function(){}
    b:undefined
    c:undefined,function(){}

}

这就好比秘书为老板准备材料,而后交给老板执行,对应预编译后执行代码,即赋值、运算、函数调用、打印这些真正干活的代码,从而得到以下如图输出

image.png

2.全局预编译

  1. 创建一个全局执行上下文 GO:{}
  2. 找全局变量声明,将全局变量名作为属性名,添加到 GO 中,值为 undefined
  3. 找全局函数声明,将全局函数名作为GO中属性名,函数体作为属性值,添加到 GO 中 同样用一个例子来深刻理解
var a 

var b = 2
function a () {
  console.log(a); 
  var c = 3
  var a = b
  function c() {}
  console.log(c);
}
a()
console.log(a);

首先执行全局预编译生成GO上下文

GO={
//     a:undefined,function(){},
//     b:undefined,
}

之后进行全局的执行代码,碰到第四行的函数会执行类似折叠操作,执行到第11行时再对functiona(){}进行函数预编译生成AO上下文

AO={
//     c:undefined,function(){},
//     a:undefined
//     // }

最后依次赋值打印输出

image.png

需要注意的是: 函数中的变量值不能越级到全局GO上下文中找。

总结

预编译四步走: 一建 AO 空对象,

二找形参变量填 undefined,

三把实参赋形参,

四找函数声明覆盖同名。

提升规则记心间: var 只提声明不提值,

函数声明整体提,

函数表达式当 var 看,

let/const 有暂死区。