引子
关于 JavaScript 提升(Hoisting),一般在实际使用的过程中,只要遵循了“先声明,后使用”的约定,很少会碰到问题。但浏览器引擎中肯定考虑各种情况,经历了这些问题,吃一堑长一智,还是总结一下。
提升
先看下面打印的是什么。
console.info(a);
a = 1;
var a;
console.info(a);
学过 JavaScript 基础就会知道 var
声明会先提升,第一个会打印 undefined
,按顺序执行后, a
被赋值,第二个打印是 1 。基于这样的解释,个人产生的疑惑会有:
- 为什么会提升?
- 这个“提升”是指声明代码都提升到最上面了吗?
带着这些疑问,去查找资料,从中了解到下面这些内容。
JavaScript 代码在执行前都要进行编译,大部分情况下编译发生在代码执行前的几微秒(甚至更短)的时间内。编译通常会经历三个步骤:
- 分词/词法分析(Tokenizing/Lexing) :这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。
- 解析/语法分析(Parsing) :这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为抽象语法树(Abstract Syntax Tree,AST)。
- 代码生成 : 将 AST 转换为可执行代码的过程称被称为代码生成。这个过程与语言、目标平台等息息相关。
JavaScript 引擎相对上面步骤要复杂得多,例如,在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。
编译的词法分析阶段基本能够知道全部(注意 eval 和 with)标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找。也就是说,变量和函数在内的所有声明会在代码执行前先被处理。因此实际上声明的代码的位置是不会变动的,而是在编译阶段被放入内存中。
提升优先级
通过上面的了解,我们知道变量和函数声明都会提升,变量名和函数名是一样的时候会如何?
// 先变量声明,后函数声明
console.info(b);
var b = 1;
function b() {
console.info(2);
}
// 先函数声明,后变量声明
console.info(c);
function c() {
console.info(3);
}
var c = 4;
通过上面的例子可以发现:函数声明比变量声明优先提升。
函数声明和函数表达式
函数声明语法如下:
function name([param[, param[, ... param]]]) { statements }
函数声明的 name
必须要有。
函数表达式和函数声明非常相似,它们有相同的语法。
var myFunction = function name([param[, param[, ... param]]]) { statements }
函数表达式的 name
非必需,写上 name
可以在调用堆栈时使用,当省去 name
时,就成了匿名函数。
函数表达式不会提升,所以不能在定义之前调用。