前言:js作用域的知识中可以了解到提升这一词,知道了变量和函数提升,但是做完一道题后我开始发现自己对提升这个还是不够了解。
代码一:
function test() {
console.log(a);
var a = 123;
console.log(a);
}
test();
代码二:
function test(a) {
console.log(a);
var a = 123;
console.log(a);
}
test(2);
代码三:
function test(a) {
console.log(a);
var a = 123;
function a(){}
console.log(a);
}
test(2);
这三段代码的结果:
我认为的: undefined, 123 \ undefined, 123 \ undefined, function a(){}
正确的: undefined, 123 \ 2, 123 \ unction a(){}, 123
如果结果跟你想的不一样的话,那就需要了解一下预编译了。
预编译发生时刻
js是单线程语言,在代码运行时会先进行语法分析,检查是否有语法错误,然后进行预编译,解释一行,执行一行。这就是js运行三个步骤:语法分析、预编译、解释执行。
js的声明和执行(赋值)是分开两步操作的,声明属于预编译环节。
js预编译分为全局预编译和函数预编译,全局预编译在js文件执行前一刻,函数预编译在函数执行前一刻。
预编译过程
全局预编译
全局预编译分为以下四步:
- 创建GO(全局/window)对象;
- 寻找var变量声明,并赋值为undefined;
- 寻找function函数声明,并赋值为函数体;
- 执行代码。
在第2步中,针对未声明就赋值的全局变量,此时是不会有变量提升和被赋值undefined的,提前使用会报错。
console.log(test)
var test = 1;
console.log(test)
function test(a) {
console.log(a);
var a = 123;
function a(){}
console.log(a);
}
function test(b) {}
test(2);
这个过程就是:
第一步:创建全局对象
GO {
}
第二步:寻找var变量声明,并赋值undefined
GO {
test: undefined
}
第三步:寻找函数声明,并赋值为函数体
GO {
test: function test(a){...} => function test(b) {...} // 遇到同名覆盖
}
第四部:执行
// 第一个console: function test(b) {...}
// 第二个console:1 ,test 被赋值为1
// 遇到函数声明,跳过,test此时为1,test(2)执行失败
函数预编译
函数预编译和全局预编译大致相同,但是会增加形参的处理,分为以下五步:
- 创建AO对象;
- 寻找形参和var变量声明,并赋值为undefined;
- 将形参和实参统一,即将形参赋值(传入函数的值);
- 寻找function函数声明,并赋值为函数体;
- 执行代码。
function test(a) {
console.log(a);
var a = 123;
console.log(a);
console.log(b);
function b(){}
}
test(2);
这个过程就是:
第一步:创建AO对象
AO{}
第二步:将变量和形参赋值为undefined
AO{
a:undefined
}
第三步:将形参和实参统一,赋值
AO{
a:2
}
第四步:将函数声明声明赋值为函数体
AO{
a:2,
b:function b(){}
}
第五步:执行
AO{
a:2,
b:function b(){}
}
// 第一个console: 2
// 第二个console:123,a被赋值为123
// 第三个console: function b(){}