文章是本人大三期间的学习笔记,一些论断取自书籍和网上博客,碍于当时的技术水平有一些写得不够好的地方,可以在评论处理智讨论~
在理解变量提升和函数提升之前,我们需要先理解好JavaScript的作用域的奇特之处。
和C、C++、以及Java不同,在ES6之前,JavaScript没有块级作用域,只有全局作用域
和函数作用域
。
**注:**其实从 ES3 发布以来,JavaScript 就有了块级作用域(
with
和catch分句
),而 ES6 引入了let
我们首先来看下面这个例子:
ps:下面代码可以直接复制出去运行哦
<script type="text/javascript">
function test() {
if(1) {
var j = 0;//j存在于test()中任何地方,不止在if{}中
for(var k = 0; k < 10; k++) {}
console.log("k = " + k);//k存在于test()中任何地方,不止在循环中
}
console.log("j = " + j);
}
test();
</script>
对于初学JavaScirpt,写惯了C、C++、Java的童鞋来说,这已经是一个很奇特的结果了。
此外,在函数作用域中,局部变量的优先级比同名的全局变量高。如果给一个局部变量或函数的参数声明的名字与某个全局变量的名字相同,那么就会有效地隐藏这个全局变量了。
在了解了关于作用域的前置知识后,我们来聊聊提升
这个有趣的现象。
首先举个栗子~
console.log(a);
var a = 2;
上面会输出什么呢?
是由于变量 a 在使用前没有先进行声明,因此会抛出 ReferenceError
异常?还是输出 2 ?
不过上面的两种猜测都不对,正确答案是输出 undefined
(已声明未定义值)。
这就是一个典型的 变量提升
现象。
当你看到 var a = 2;
时,可能会认为这只是一个简单的声明语句。但 JavaScript 实际上会将其看成两个声明: var a;
和 a = 2;
。第一个定义声明是在编译阶段
进行的,第二个赋值声明会被留在原地等待执行阶段
。
因此上面的代码会以如下的形式进行处理:
var a;
console.log(a);
a = 2;
因此,打个比方,这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面。这个过程就叫做 提升
。
注:无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最前端,这个过程被称为
变量(函数)提升
接着让我们来结合函数以及作用域以及上面的变量提升来看一个有趣的题目(这极有可能会出现在面试题中):
var a = true;
foo();
function foo() {
if(a) {
var a = 10;
}
console.log(a);
}
这个例子最终的答案是 undefined
。这是个很有趣的题目包含了许多可能会在 JavaScript 中碰到的坑。
下面是这段代码实际会被 JavaScript 执行的样子:
function foo() {
var a;
if(a) {
a = 10;
}
console.log(a);
}
var a;
a = true;
foo();
var a = true;
被解析成上面第1、2行不足为奇。
首先,我们来看 foo(...) {}
的位置被移到了 foo();
的前面,根据前面对变量提升
的解释,我们可以很容易理解这是函数发生了提升,这就是函数提升
。
继续我们来分析 foo(...) {}
中的代码。为什么会输出 undefined
,而不是我们期望的 10
,玄机都在其中!
让我们回到最前面对作用域的理解,在 JavaScript 中没有块级作用域,所以 var a = 10;
会被 JavaScript 分为两步中的 var a;
会被提升到函数作用域中的最顶端,声明了一个局部变量 a,在 foo(...) {}
的函数作用域中,这个重名局部变量 a 会屏蔽全局变量 a,换句话说,在遇到对 a 的赋值声明之前,在 foo(...) {}
,a 的值都是 undefined
!
所以一个 undefined 的 a 进入不了 if(a) {...}
中,所以最后被打印出来的是 undefined
。
需要注意的一点是,在 JavaScript 中,函数有两种方式进行声明,函数声明会被提升,但是函数表达式却不会被提升。
将上面的例子进行修改:
var a = true;
foo();
var foo = function() {
if(a) {
var a = 10;
}
console.log(a);
}
这里会抛出 TypeError
,而不是 ReferenceError
。
同常量提升
,代码会被解析为:
var a;
var foo;
a = true;
foo();
foo = function() {
if(a) {
var a = 10;
}
console.log(a);
}
在执行阶段时,当碰到 foo();
时,foo 还没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。foo();
由于对 undefined
值进行函数调用而导致非法操作,因此会抛出 TypeError
异常。(关于TypeError
和ReferenceError
的区别,可移步 TypeError 和 ReferenceError )。
函数声明和变量声明都会被提升。但是有一个需要注意的细节是函数会首先被提升,然后才是变量
。
考虑下面的代码:
foo();
function foo() {
console.log('1');
}
var foo = function() {
console.log('2');
}
会输出 1 而不是 2!这个代码片段会被引擎理解为如下形式:
function foo() {
console.log('1');
}
foo();
foo = function() {
console.log('2');
}
然后,如果是两个函数声明,出现在后面的函数声明可以覆盖前面的。
foo(); //2
function foo() {
console.log('1');
}
function foo() {
console.log('2');
}
上面的代码会被解析成:
function foo() {
console.log('1');
}
function foo() {
console.log('2');
}
foo(); //2