提升

我们都知道对于任何声明在某个作用域内的变量,都将属于某个作用域。但是对于JS而言,变量的声明与作用域还存在一种微妙的联系,这种联系我们叫做提升

首先来看一个例子:

1
2
3
4
5
console.log(name); // undefined
var name = 'limoer';
age = 2;
var age;
console.log(age); // 2

这个例子很好的体现了提升,输出name的时候,由于变量的声明在输出语句之后,想当然的使用RHS查找,查找失败抛出错误。但实际情况却输出了undefined,这是因为对于变量name,其声明被提升了,但是赋值语句却没提前,所以输出undefined。而对于age变量,赋值在输出前,我们理所应当的认为前面的赋值会被覆盖,但是程序却出乎意料的输出了2,这同样是因为变量age的声明提升到了首部,然后再进行赋值,最后输出了结果2。

上面的例子可以改写成这样:

1
2
3
4
5
6
var name;
console.log(name);
name = 'limoer';
var age;
age = 2;
console.log(age);

其实,对于提升的讨论就是“先有蛋还是先有鸡”的讨论,通过上面的分析,显然我们可以得出“先有蛋(声明),后有鸡(赋值)”的结论。

继续看一个例子:

1
2
3
4
5
6
7
8
9
10
foo(); // call foo
function foo(){
console.log('call foo');
console.log(a);
var a = 1;
}
bar(); // TypeError
var bar = function(){
console.log('call bar');
}

这个例子说明了三个问题:
(1)函数声明存在提升;
(2)函数表达式不存在提升;
(3)每个作用域都会存在提升。
这里我只想解释一下为什么上面运行bar() 抛出类型错误,由于变量声明存在提升,所以实际执行的是下面的代码:

1
2
3
var bar;
bar();
bar = function (){...}

由于bar的提升,调用bar()的时候并没有发生赋值操作,此时bar为undefined,所以对undefined进行函数调用会抛出一个TypeError而不是ReferenceError。

那么问题来了,既然变量和函数声明都存在提升,那么当这两个同时出现的时候,谁的优先级更高呢?

同样,写一个例子如下:

1
2
3
4
5
6
7
8
foo(); // call foo function
var foo;
function foo(){
console.log('call foo function');
}
foo = function (){
console.log('call foo expressions');
}

好了,我们得出的结论是:函数声明提升的优先级更高。

注意:这里尽管foo的声明在函数foo声明之前,但是由于存在这样的规则所以被当做重复声明被忽略了,但是如果是函数声明,照样可以覆盖前面的声明。

小结

ES6引入的const和let关键字的一大特点就是使用这两个关键字声明的标识符不存在提升,这就意味着无法引用未声明的标识符,从而可以避免由提升带来的一些列问题。

对于var a = 1;这样常见的变量声明方式,我们应该把它想象成两个步骤:首先是在预编译阶段对变量a进行声明,接下来是执行阶段,对a进行赋值,而提升则是将所有的声明“移动”到作用域顶部的过程。加油!

分享到 评论