总结:先有声明再有赋值
编译器
JS引擎在解释JS代码之前会首先对其进行编译,编译阶段的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。
所以准确的思考方式:包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理
当你看到var a = 2时,可能会以为这是一个声明,但JS会将其看成两个声明
var aa = 2
第一个声明在编译阶段进行,第二个赋值会留在原地等待执行阶段
这个过程就好像变量和函数声明从它们的代码中出现的位置被移动到了最上面,这个过程就是提升
注意:
- 只有声明本身会被提升,而赋值或其他运行逻辑会留在原地,如果提升改变了代码的执行顺序,会造成非常严重的破坏
- 每个作用域都会进行提升操作,尽管前面大部分都代码片段已经简化了(因为它们只包含全局作用域),而我们正在讨论的foo(..)函数自身也会在内部对
var a进行提升(显然并不是提升到了整个程序的最上方)
foo()
function foo(){
console.log(a);
var a = 2;
}
上面的代码会被理解为下面的形式
function foo(){
var a;
console.log(a); // undefined
a = 2;
}
foo();
- 即便是具名的函数表达式,名称标识符在赋值之前也无法在所在的作用域中使用
foo();
bar();
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);
}
上面的代码输出的是1而不是2,因为这个代码会被理解成下面的形式
function foo(){
console.log(1);
}
foo();
foo = function(){
console.log(2);
};
尽管var foo出现在function foo() ... 之前,但他是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前,但是出现在后面的函数声明还是可以覆盖前面的。
foo(); // 3
function foo(){
console.log(1);
}
var foo = function(){
console.log(2);
}
function foo(){
console.log(3);
}