接着上一篇文章JavaScript闭包之作用域和词法作用域,今天我们继续来说说闭包的前置知识–提升。


在上一篇文章《JavaScript闭包之作用域和词法作用域》的最后,我们有这样的一个例子

1
2
3
4
5
6
7
function foo () {
console.log(a); // 1
console.log(b); // undefined
}
var a = 1;
foo();
var b = 1;

当时只是简单提了提升,今天我们就来看看提升

顺序执行?

相信大部分人在写JavaScript时,都存在一个潜意识–JavaScript代码时一行一行的,毕竟:

1
2
3
4
5
var a = 2;
function bar() {
console.log(a)
}
bar()

但实际上说JavaScript代码时顺序执行的并不完全正确,我们来看看一些特殊情况

1
2
3
a = 2;
var a;
console.log(a)

你认为此时console.log(a)会输出什么?
很多开发者会认为此时输出undefined, 因为在第二步重新声明了变量a, 但是并没有给a赋值,所以a被赋值了默认值undefined。但是,真正的输出还是2。
我们再来看看另外一段代码

1
2
console.log(a)
var a = 2

受上一个例子的影响,很容易就认为和上一个例子出现同样的行为,结果还是输出2, 或者还有部分人会认为因为a没有提前定义,所以输出RefenerceError异常,但是这两种主观意识都是错误的,此时真正输出的是undefined。
为什么会出现这样的情况呢?编译器疯了吧!!!

变量提升

刷过面试题的小伙伴一定知道,JavaScript代码存在一个预编译的过程,在这个过程中,在这个过程中,编译器会一段一段的分析和执行,而不是所谓的一行一行的。在上一篇文章中,我们提到LHS和RHS这两个概念时说到,var a = 2这个过程有一次RHS和一次LHS(不理解为什么的童靴可以看看上篇博客JavaScript闭包之作用域和词法作用域),
RHS是查找a的值,LHS是将2赋值给查找到的变量a。从这儿我们就可以看出来,var a = 2;分成了两部分执行,var a和a = 2。第一个定义声明是在编译时完成的,第二个赋值声明是在执行时完成的。
所以上面的例子就可以改写为:

1
2
3
var a;
a = 2;
console.log(a)

这个过程就像是将声明移动了作用域的最上面。这个过程就叫做提升。
注意:变量的提升是在编译阶段完成的。代码执行到赋值语句时才会给声明的变量赋值。

函数提升

在JavaScript中,不仅仅是变量会提升,函数在编译阶段也会提升

1
2
3
4
5
foo();    // 1
foo() {
var a = 1;
console.log(a)
}

在上面代码中,foo函数声明就被提升了,所以在第一行调用就可以正常执行。

1
2
3
4
5
foo();      // undefined
function foo() {
console.log(a)
var a = 2;
}

了解了变量提升,我们可以很容易的知道函数输出undefined。在JavaScript编译的过程中,foo函数的声明被提升了,同时提升的还有foo函数的作用域,并且作用域中也发生了变量提升。所以我们可以看成这种形式

1
2
3
4
5
6
funcation foo() {
var a;
console.log(a);
a = 2;
};
foo();

但是函数的提升仅限于函数声明,函数表达式并不会被提升

1
2
3
4
5
bar()  // TypeError
var bar = function foo() {
var a = 2;
console.log(a)
}

注意这里是TypeError, 而不是ReferenceError。在编译阶段,foo被声明并且提升了,但是并没有赋值,只有运行时才会被赋值foo函数声明,所以此时bar还是undefined,foo()对undefined进行函数调用从而导致非法操作,所以最后抛出TypeError。

谁先谁后

说完了变量提升和函数提升之后,你可能会有个疑问,函数提升和变量提升谁先发生。
下面我们先来看一段代码

1
2
3
4
5
6
7
8
9
10
11
foo();  // 1

var foo;

function foo () {
console.log(1)
}

foo = function() {
console.log(2)
}

注意:尽管var foo出现在function foo() {…} 之前,但它是个重复声明,在这儿会被忽略。所以在编译阶段,先声明的是函数,然后才是变量。
在变量声明时,有这样的规则:变量提升时,如果存在同名的变量,则直接覆盖吗,如果存在同名的函数声明,则忽略变量声明。

为什么有提升?

世界上没有无缘无故的事情,JavaScript也是一样,提升在JavaScript中意义重大,下面我们来看看这样的一个例子

1
2
3
4
5
6
7
function foo () {
bar()
};
bar ();
function bar () {
foo();
}

上面的代码只是一个简单的循环引用的例子,试想一下,如果没有提升,上面的代码肯定不能正常执行,但是有了变量提升,循环引用也可以实现了。


本文介绍了JavaScript提升的概念,不是很难,但是很重要。接下来重头戏到了,欢迎关注。
本文完