探析js引擎的预编译机制
引言
声明提升指的是变量和函数的声明会被提升到当前作用域的顶部,但变量的初始化并不会被提升,看以下两个简单例子:
变量的声明提升——
console.log(a); // 打印 undefined
var a = 10;
// 相当于
// var a
// console.log(a);
// a = 10
函数声明会整体提升——
test(); // 成功调用,打印出20
function test() {
var a = 20;
console.log(a);
}
// 相当于
// function test() {
// var a = 20;
// console.log(a);
// }
// test();
于是,在大多数的书籍中,你都能看见这两句话:
- 变量声明,声明提升
- 函数声明,整体提升
但事实上碰到面试官出题时,只记这两句话往往不能帮你快准狠地得出正确答案,所以接下来我们就来探析js的预编译机制,真正理解声明提升背后的原理
前置知识
- AO:活动对象(Activation Object)。当一个函数��调用时,JS引擎会创建一个新的执行上下文,并在其中构建活动对象,其中包含了函数的参数、局部变量、函数声明
- GO:全局对象(Global Object)。存放所有的全局变量和函数,以及内置的对象和方法。在浏览器环境下,全局对象通常是
window
对象;而在 Node.js 环境中,全局对象则是global
对象
预编译
代码在执行前需要进行编译操作,用于确定代码之间的各种关联 。编译总是发生在函数执行的前一刻。
- 全局预编译三部曲:
- 创建GO对象
- 找变量声明,将变量名作为GO的属性名,值为undefined
- 在全局找函数声明,将函数名作为GO的属性名,值为该函数体
- 函数体内预编译四部曲:
- 创建一个AO对象
- 找形参和变量声明,将形参和变量名作为AO的属性名,值为undefined
- 形参和实参统一(因为对象里面的key是不可能重复的)
- 在函数体内找函数声明,将函数名作为AO的属性名,值为该函数体
举两个栗子
var a = 10;
function fn(a) {
var a = 2;
function a() {}
console.log(a);
}
fn(3)
- 首先创建GO对象,其中包含变量声明a值为undefined、函数体fn
- 执行到第七行代码,调用函数fn时,创建一个AO对象
- 找到形参a和声明的变量a,统一为a=3
- 找到函数声明a,将a重新赋值,a=[Function: a]
- 预编译完成,开始执行代码
- 执行到第三行将a重新赋值2,第四行不是可执行代码,所以最后打印出来a为2
第二个例子同理,快来自己试试看吧:
function fn(a) {
console.log(a);
var a = 123
console.log(a);
function a() {}
console.log(a);
var b = function() {} // 函数表达式
console.log(b);
function c() {} // 函数声明
var c = a
console.log(c); // 123
}
fn(1)
依次打印出:[Function: a] 123 123 [Function: b] 123
认真读完后相信你已经对js预编译有了更加深刻的理解,阅读过程中若发现有任何不足或亮点,欢迎大家在评论区指出交流,我们共同进步~
转载自:https://juejin.cn/post/7398352986324664346