我们直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的。但实际这并不完全正确
a = 2;var a;console.log(a);
这里可能会认为是 undefined,因为 var a 声明在 a = 2 之后。实际输出了 2。
console.log(a);var a = 2;
鉴于上面的代码可能会是 2,还有人认为可能会报异常 ReferenceError,不幸这两种猜测都不对,输出来的是 undefined
要搞明白这个,要明白编译的原理,在
编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。这也是词法作用域的核心内容
正确的思路是,
包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
当你看到 var a = 2; 时,可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个声明:var a 和 a = 2;第一个定义声明是在编译阶段进行的,第二个赋值声明会被留在原地等待执行。所以第一段代码会以如下形式进行处理
var a;a = 2;console.log(a); //2
第二段代码实际按一下流程处理
var a;console.log(a); // undefineda = 2;
这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面。这个过程就叫做提升。每个作用域都会进行提升操作,函数自身也会对内容 var a 进行提升(显然并不是提升到整个程序的最上方)
foo(); // TypeErrorbar(); // ReferenceErrorvar foo = function bar(){ // ...}
可以看到函数声明被提升,但是函数表达式却不会被提升。这段程序的 foo 被提升并分配到所在作用域,因此不会导致 ReferenceError,而是抛出了 TypeError: foo is not a function。bar 抛出了 ReferenceErro。上段代码实际上会被理解为
var foo;foo();bar();foo = function() { var bar = ...self... //...}
变量和函数都会被提升。但是函数会首先被提升,然后才是变量
foo(); // 1var foo;function foo() { console.log(1);}foo = function(){ console.log(2)}
这个代码会被引擎理解成如下形式
function foo() { console.log(1)}foo(); // 1foo = function() { console.log(2)}
尽管重复的声明会被忽略掉,但是后面的函数声明还是可以覆盖前面的
foo();var a = true;if(a){ function foo() { console.log(‘a‘) }}else{ function foo(){ console.log(‘b‘) }}
这里 foo 输出了 b ,不是我们想要的结果,因为声明被覆盖掉了,所以需要注意这种行为并不可靠,尽可能避免在块内部声明函数