写在前面
最近在读一本书《你不知道的JavaScript(上)》
,刚好读到了提升这个章节。声明提升早在之前的公开课上就有所了解,不过那时候第一次接触还不是很清楚。当再次读到它的时候仿佛故友重逢一般,从中得到了更加深刻的认识,然后就做一下笔记(个人理解)。
然后这本书写的还是蛮好的,推荐大家有时间读一读,作者讲的比较风趣,而且通俗易懂。
声明提升
划一下重点
- 声明提升在预编译阶段
- 函数,变量的声明都会被提升,且函数会提到变量前面
- 变量提升是不会超出当前作用域的
- 被提升的只有声明哦,其他语句还是在原来的位置
变量声明提升
{
a=5;
var a;
console.log(a); //5
}
如果按照从上到下理解的思路,a的声明在赋值后面,a会被重新赋值为undefined
但实际上并不是这样,在编译阶段a的声明语句会被提升
,上面的代码其实和下面的代码是等价的
{
var a;
a=5;
console.log(a); //5
}
注意:只有声明会被提升,其他语句是保留在原地的,看看下面的代码:
{
console.log(a); //undefined
var a = 2;
}
按照上面的声明提升,有人可能会认为是ReferenceError,但实际结果是undefined,为什么呢?
上面的代码和下面的代码是等价的:
{
var a;
console.log(a); //undefined
a = 2;
}
就像之前说的,提升的只是变量的声明,var a = 2;
其实是分两部分的
var a; //声明
a = 2; //赋值(执行阶段)
预编译四部曲
先了解下预编译,理解了预编译,可能对于后面的提升会有更好理解
- 创建AO对象(执行期上下文)
- 找到形参和变量声明,并将形参名和变量名作为AO对象的属性,默认值undefined
- 实参与形参统一
- 在函数体中找到函数声明,赋值
函数声明提升
函数声明提升和变量是类似的,不过函数声明会提到变量的声明之前
,看下面的例子:
{
var a = 2;
console.log(a); //2
function a() {
a = 6;
}
a(); //TypeError
}
事实上,上面的代码是有问题的,a是没有办法作为函数执行的,上面的代码可以看作这样:
{
//编译
var a = undefined;
a = function a(){
a = 6;
}
//执行
a = 2;
console.log(a); //2
a(); //TypeError
}
可以看到,编译阶段首先声明a,然后给a赋值为function(){},之后开始执行,但是在一开始a
就a=2
被覆盖了,所以a不再是一个函数
。
下面是一个我曾经做过的练习,是一个综合的例子:
function fn(a){
console.log(a);
var a = 123;
console.log(a);
function a(){}
console.log(a);
console.log(b);
var b = function(){}
console.log(b);
}
fn(1);
- 关于这个例子我要说明一点,在fn()内部 ,传参发生在最前面,这一点我还不晓得为啥子。
先不着急看结果,先一步步探讨代码编译、执行过程,上面的代码经过预编译之后:
var fn;
fn = function(a){
//编译
var a = undefined;
var b = undefined;
a = 1; //传进来的参数
a = function a(){} //a函数声明提升
//执行
console.log(a);
a = 123;
console.log(a);
//function a(){}
console.log(a);
console.log(b);
b = function(){} //这里是函数表达式,只会提升变量b的声明,赋值会保留
console.log(b);
}
fn(1);
所以结果是:
function a(){}
123
123
undefined
function (){}
或许一开始接触可能会有些头皮发麻,其实只要能够清楚变量和函数提升的规则,理解起来就比较容易了!