变量和函数声明从代码中出现的位置被“移动”到了最上面,这个过程就叫做提升,但只有声明本身会被提升,赋值或其他运行逻辑会留在原地,并且每个作用域都会进行提升操作。
提升包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理
例如:var a = 2 ; 会被JavaScript堪称两个声明:var a;和a = 2;。第一个声明在编译阶段进行,第二个赋值声明会被留在原地等待执行阶段。
// 原代码 var a = 2; console.log(a); // 实际处理流程 var a; a = 2; console.log(a); // 2
变量和函数声明从代码中出现的位置被“移动”到了最上面,这个过程就叫做提升。
但只有声明本身会被提升,赋值或其他运行逻辑会留在原地,并且每个作用域都会进行提升操作。
// 原代码 foo(); function foo() { // 函数声明 console.log(a); // undefined var a = 2; } // 实际处理流程 function foo() { var a; // 此时a未被赋值 console.log(a); // undefined a = 2; } foo();
可以看到,函数声明会被提升,但函数表达式却不会被提升。
// 原代码 foo(); var foo = function bar() { // ... }; // 实际处理流程 var foo; foo(); foo = function bar() { // ... };
由于foo被提升并分配给作用域,所有foo()不会导致ReferenceError,但此时的foo没有赋值,foo()由于对undefined值进行函数调用而导致非法操作,因此抛出TypeError异常。
即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用:
foo(); // TypeError bar(); // ReferenceError var foo = function bar() { // ... }
这个代码片段经过提升会被理解为一下形式:
var foo; foo(); // TypeError bar(); // ReferenceError foo = function() { var bar = ...self... // ... }
函数优先
foo(); // 1 var foo; function foo() { console.log(1); } foo = function() { console.log(2); }
输出的是 1 而不是 2 的原因是这个代码片段会被引擎理解为如下形式:
function foo() { console.log(1); } foo(); // 1 foo = function() { console.log(2); }
由于函数声明会被提升到普通变量声明之前,导致function foo()...之后的var foo是一个重复的声明而被忽略了。
尽管重复的var会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的
小结
JavaScript引擎会将var a = 2;当作两个单独的声明,第一个是编译阶段的任务,第二个是执行阶段的任务。
这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。这个过程可以形象的表示成所有声明都会被“移动”到各自的作用域的最顶端,这个移动的过程就叫做提升。
声明本身会被提升,但包括函数表达式的赋值在内的赋值操作并不会提升。
避免重复声明,特别是当普通的var声明和函数声明混合在一起的时候。