引言
你是否在深夜独自写js代码时因为js的v8引擎独特的声明提升机制而苦恼,捶胸顿足,明明变量写在后面,输出不会与其他底层语言一样报错,而是打印undefined,这就是你不深刻理解声明提升背后的预编译机制的后果,那么接下来,让我带你一文吃透js的预编译。
1.声明提升的“坑”
首先,让我们看一段代码,看看所谓的声明提升的“坑”
console.log(a);
var a = 10;
会打印输出什么呢?没想到吧,输出undefined,完全偏离了人的先定义,后输出的正常思维,竟然没有报错,还输出undefined。其实这是v8独有声明提升机制等效以下代码
var a;
console.log(a);
a = 10;
但当代码稍微复杂一点时,我们就不能单单只靠死记硬背声明提升从而判断代码输出结果,这时,我们就要引入预编译机制,了解v8执行的底层逻辑,才能做到不踩“坑”
预编译机制的执行过程
预编译包括函数预编译和全局预编译,同时- 预编译发生在代码执行之前,但并不是“一次性编译完所有代码”,函数的预编译是在“函数被调用时”才触发的,全局预编译在代码加载时触发。
1.函数预编译
- 创建一个执行上下文 AO:{}
- 找形参和变量声明,将形参和变量名作为属性名,添加到 AO 中,值为 undefined
3.将形参和实参统一
- 在函数体中找函数声明,将函数名作为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(){}
}
这就好比秘书为老板准备材料,而后交给老板执行,对应预编译后执行代码,即赋值、运算、函数调用、打印这些真正干活的代码,从而得到以下如图输出
2.全局预编译
- 创建一个全局执行上下文 GO:{}
- 找全局变量声明,将全局变量名作为属性名,添加到 GO 中,值为 undefined
- 找全局函数声明,将全局函数名作为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
// // }
最后依次赋值打印输出
需要注意的是: 函数中的变量值不能越级到全局GO上下文中找。
总结
预编译四步走: 一建 AO 空对象,
二找形参变量填 undefined,
三把实参赋形参,
四找函数声明覆盖同名。
提升规则记心间: var 只提声明不提值,
函数声明整体提,
函数表达式当 var 看,
let/const 有暂死区。