引擎会在解释JavaScript 代码之前首先对其进行编译,编译阶段中的一部分工作就是找到所有的申明,并用合适的作用域将它们关联起来。其中变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
分析以下代码
console.log(a); var a=2;
当你看到 var a=2;时,可能会认为这是个声明,但JavaScript实际上会将其看成两个声明: var a; 和a=2;第一个定义声明是在编译阶段进行的,第二个赋值声明会被留在原地等待执行阶段。
这个代码段会被解析为
var a; console.log(a); a=2;
其中第一部分是编译,而第二部分是执行,因此,这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面,这个过程叫做提升。只有声明本身会被提示,而赋值或其他运行逻辑会留在原地。
对于每个作用域而言,每个作用域内都会进行提升操作 。
foo(); function foo(){ console.log(a);//undefined var a=2; }
会被编译成
function foo(){ var a; console.log(a);//undefined a=2; } foo();
可以看到,函数声明会被提升,但是函数表达式却不会被提升。
foo(); //不是ReferenceError,而是TypeError! var foo=function var bar(){ //... }
这段程序中的变量标识符foo() 被提升并分配给所在作用域(在这里是全局作用域),因此foo()不会导致ReferenceError。但是foo 此时并没有赋值( 如果它是一个函数声明而不是函数表达式,那么就会赋值)。 foo()对于undefined值进行函数调用而导致非法操作,因此抛出TypeError 异常。
注:TypeError 是在作用域中找到了,但是做了它不能做的事情。
ReferenceError 是在作用域中找不到
同时也要记住,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用;
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(); foo=function(){ console.log(2) };
注意,var foo 尽管出现在 function foo(){}...的声明之前,但是它是重复声明的(因此被忽略了)因为函数声明会被提升到普通变量之前。
尽管重复的var 声明会被忽略掉,但出现后面的函数声明还是可以覆盖前面的。
foo();//3 function foo(){ console.log(1); } var foo=function(){ console.log(2) } function foo(){ console.log(3); }