先有鸡还是先有蛋
直觉上我们会认为 Javascript 的代码是一行一行由上到下依次执行的。但实际上并不完全正确,有一种情况下这个假设是错误的。
console.log(a); // undefined
var a = 2;
编译器
引擎会 在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。
正确的思路就是:包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
当你看到 var a = 2;
时,可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个声明:var a;
和 a = 2;
。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。
这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动” 到了最上面。这个过程就叫作提升。
换句话说,先有蛋(声明)后有鸡(赋值)。
只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。如果提升改变了代码执行的顺序,会造成非常严重的破坏。
foo();
function foo() {
console.log(1);
}
foo 函数的声明(这个例子还包括实际函数的隐含值)被提升了,因此第一行中的调用可以正常执行。(也就是说函数声明可以被整体提升到当前作用域的最顶部、函数表达式只会提升变量声明,赋值不会被提升)
同时也要记住,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {};
经过提升之后会变成
var foo;
foo();
bar();
foo = function() {
var bar = self;
};
函数优先
函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个 “重复”声明的代码中)是函数会首先被提升,然后才是变量。
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
};
引擎会理解为
function foo() {
console.log(1);
}
foo(); // 1
foo = function() {
console.log(2);
};
注意,var foo 尽管出现在 function foo()... 的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。 尽管重复的 var 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。
foo(); // 3
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
};
function foo() {
console.log(3);
}
❗️总结一下:
同名的函数声明和变量声明,函数声明被优先提升,忽略变量声明;同名的函数声明,后面会覆盖前面
小结
我们习惯将 var a = 2;
看作一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a;
和 a = 2;
当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。
提升:所有的声明(变量-var;函数-function)都会被'移动'(提升)到作用域的最顶端。
声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候,会引起很多危险的问题。
网友评论