提升
示例
分别在 script
元素内和控制台内编写以下代码并执行:
console.log(foo);
var foo = 1;
如果在控制台中,因为 foo
还没有被声明,所以对 foo
变量取值打印的时候程序会报错,提示 foo
未定义。然而在 script
元素内编写上述代码并执行时,代码可以顺利执行,foo
被打印出了 undefined
。发生这种情况是因为 JS 代码的运行方式。
先编译再解释
JS 的代码运行过程分为两个阶段,编译和解释,代码会在 编译后再解释,变量和函数的声明以及确定作用域的过程都是发生在编译阶段。
如果把一个完整的声明语句分为两部分:
var bar = 1;
拆分为:
var bar;
bar = 1;
声明部分的代码会在编译阶段进行,而其他部分的代码会在解释阶段执行。这种运行方式与变量声明和函数声明所在的位置无关。比如:
console.log(foo);
bar();
var foo = 1;
function bar() {
console.log("bar function");
}
上面的代码会按照一下方式运行:
// 所有声明在编译时处理,所以先执行,这里可以理解为编译阶段
var foo;
function bar() {
console.log("bar function");
}
// 然后按照顺序小执行后续代码,这里是解释阶段
console.log(foo);
bar();
foo = 1;
所以在 JS 中,声明像是被放到了代码开头执行,这个过程被叫做 提升,不论变量还是函数声明,都会产生提升。
函数优先
在编译阶段时,变量声明在前,函数声明在后。所以当出现变量声明与函数声明同名时,会产生函数声明覆盖,同名的变量会函数声明覆盖:
比如以下代码:
foo(); // 1
// 这是函数表达式,不会提升,只是声明会提升
foo = function () {
console.log(2);
};
foo(); // 2
function foo() {
console.log(1);
}
var foo;
实际执行过程应该如下:
// 编译阶段,变量声明在前,函数声明在后
var foo;
// 函数声明产生覆盖,所以函数优先
function foo() {
console.log(1);
}
// 以下是解释阶段,顺序执行
foo(); // 1
foo = function () {
console.log(2);
};
foo(); // 2
隐式声明不会提升
因为隐式声明没有 var
关键字,引擎无法在编译阶段识别出隐式声明,所以隐式声明不会产生提升。隐式声明发生在解释阶段:
console.log(foo); // error
foo = 0;
上诉代码会发生错误,因为对 foo
取值时,还没有声明。
函数内部也会先编译再执行
函数执行时,也会先编译再解释,所以函数内部也会发生提升:
function foo() {
console.log(bar);
var bar;
}
foo();
上面代码中,bar
的声明也会被提前,所以打印 bar
不会报错,是默认值 undefined
。
总结
var bar = 1
会被拆分为var bar;
和bar = 1
分别执行,声明会在编辑阶段生效,赋值会在解释阶段有效。var
声明和函数声明在 JS 执行中被率先处理,这个过程被叫做提升。- 在提升中,函数优先。
- 如果你觉得本节内容较难,你可以这样阅读代码,把同一作用域内的所有变量声明和函数声明放到程序的最前,并函数放到变量后面,这样就是程序执行的顺序。