JavaScript闭包之提升
接着上一篇文章JavaScript闭包之作用域和词法作用域,今天我们继续来说说闭包的前置知识–提升。
在上一篇文章《JavaScript闭包之作用域和词法作用域》的最后,我们有这样的一个例子
1 | function foo () { |
当时只是简单提了提升,今天我们就来看看提升
顺序执行?
相信大部分人在写JavaScript时,都存在一个潜意识–JavaScript代码时一行一行的,毕竟:
1 | var a = 2; |
但实际上说JavaScript代码时顺序执行的并不完全正确,我们来看看一些特殊情况
1 | a = 2; |
你认为此时console.log(a)会输出什么?
很多开发者会认为此时输出undefined, 因为在第二步重新声明了变量a, 但是并没有给a赋值,所以a被赋值了默认值undefined。但是,真正的输出还是2。
我们再来看看另外一段代码
1 | console.log(a) |
受上一个例子的影响,很容易就认为和上一个例子出现同样的行为,结果还是输出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 | var a; |
这个过程就像是将声明移动了作用域的最上面。这个过程就叫做提升。
注意:变量的提升是在编译阶段完成的。代码执行到赋值语句时才会给声明的变量赋值。
函数提升
在JavaScript中,不仅仅是变量会提升,函数在编译阶段也会提升
1 | foo(); // 1 |
在上面代码中,foo函数声明就被提升了,所以在第一行调用就可以正常执行。
1 | foo(); // undefined |
了解了变量提升,我们可以很容易的知道函数输出undefined。在JavaScript编译的过程中,foo函数的声明被提升了,同时提升的还有foo函数的作用域,并且作用域中也发生了变量提升。所以我们可以看成这种形式
1 | funcation foo() { |
但是函数的提升仅限于函数声明,函数表达式并不会被提升
1 | bar() // TypeError |
注意这里是TypeError, 而不是ReferenceError。在编译阶段,foo被声明并且提升了,但是并没有赋值,只有运行时才会被赋值foo函数声明,所以此时bar还是undefined,foo()对undefined进行函数调用从而导致非法操作,所以最后抛出TypeError。
谁先谁后
说完了变量提升和函数提升之后,你可能会有个疑问,函数提升和变量提升谁先发生。
下面我们先来看一段代码
1 | foo(); // 1 |
注意:尽管var foo出现在function foo() {…} 之前,但它是个重复声明,在这儿会被忽略。所以在编译阶段,先声明的是函数,然后才是变量。
在变量声明时,有这样的规则:变量提升时,如果存在同名的变量,则直接覆盖吗,如果存在同名的函数声明,则忽略变量声明。
为什么有提升?
世界上没有无缘无故的事情,JavaScript也是一样,提升在JavaScript中意义重大,下面我们来看看这样的一个例子
1 | function foo () { |
上面的代码只是一个简单的循环引用的例子,试想一下,如果没有提升,上面的代码肯定不能正常执行,但是有了变量提升,循环引用也可以实现了。
本文介绍了JavaScript提升的概念,不是很难,但是很重要。接下来重头戏到了,欢迎关注。
本文完