函数作用域和变量声明提升
前言
在日常开发中,有时候会遇到一些比较怪异的问题,甚至会骂这是什么破语言啊。这次我要讲的正是怪异 JS 问题中的冰山一角:函数作用域以及声明提前。其实如果理解了它的运行机制,问题也不算是怪异了。先试试下面的题目,如果都答对了(答案在底部),那这篇文章就不需要继续看下去了。
题目 1:
1 | a(); |
题目 2:
1 | var scope = 'global'; |
以上的题目先不做讲解,我们先来理解一下几个概念:变量声明、函数作用域和声明提前。
变量声明
我们知道,javascript 是一门解释型语言,执行一段 javascript 代码会经过下面的过程。

在 javascript 的标准规范里,在使用一个变量之前应该需要经过声明(虽然在非严格模式下,未声明的变量会被当前全局变量使用,但是从代码可读性和维护性上来说,推荐变量使用前声明的方式),就是我们常写的var。比如:
1 | var a, b, c; |
因为上面的变量a,b,c并没有给它们赋值,因此他们的值都是undefined。
注意:在同一作用域里,重复声明的变量,会被当作普通的赋值操作。
函数作用域和声明提前
javascript 并没有类似 C 语言那样的块级作用域,因此,这也是常常给开发者带来的困惑,比如下面的例子:
1 | for (var i = 0; i < 10; i++) { |
你可能会觉得输出的i应该是undefined,其实不对,变量i依然是可以访问的。所以这样看来,javascript 确实不存在块级作用域(不过也有例外,在新的 JS 规范里,用let定义的变量是支持块级作用域的,这里不做讨论),取而代之的是函数作用域,而且也是唯一拥有自身作用域的结构。
函数作用域是指在函数体内声明的所有变量都是始终可见的,而且有意思的是,甚至在变量声明之前就已经可以允许访问,这种特性被称作**声明提前(hoisting)**,函数声明提前的意思是,在函数体内所有的变量(不包括赋值操作),都会被提前到函数体的顶端。比如下面的例子:
1 | function a() { |
其实在 javascript 的预编译阶段里,会扫描整个 a 函数的代码,然后把变量 b 提前到函数的顶端,并赋值为undefined,所以上面的代码相当于:
1 | function a() { |
同样的,假设 b 为函数时,也会被提前到函数顶端,如:
1 | function a() { |
上面的例子也是我们经常纠结的地方,为什么 b 能调用,而 c 调用不了呢。
还有一种特殊情况,假设函数作用域内,变量名和函数名重复的情况下,到底那个优先呢,不妨验证下:
1 | function a() { |
是的,函数声明提前比变量声明的优先级更高。可以查阅javasript MDN。

看到这里,我想前面那两道题,你应该可以很轻松的知道答案了。
答案
题目 1 答案:2,2,2,3
题目 2 答案:undefined,local
参考资料
- 《javascript 高级程序设计》
- 《javascript 权威指南》
- javascript MDN
[本文谢绝转载,谢谢]