变量提升
首先重申两个错误类型:
- ReferenceError(引用错误):在作用域中找不到;
- TypeError(类型错误):作用域中找到了,但是做了它不可能做的事情。
示例1:
console.log(fn); // ƒ fn () {}
console.log(a); // undefined
console.log(b); // ReferenceError: Cannot access 'b' before initialization
console.log(c); // ReferenceError: Cannot access 'c' before initialization
var a = 2;
let b = 2;
const c = 2;
function fn () {};
示例1的例子说明:
- var声明的变量和函数会在任何代码执行前被处理,在初始化之前被提升到了当前作用域顶层,变量声明被默认先行赋值为undefined的值。let、const不存在变量提升。
- var a = 2,会经历两个阶段:
- var a,这是在编译阶段进行的;
- a = 2,赋值声明会被留在原地等待执行阶段执行。
- 先有声明,后有赋值.
示例2
console.log(fn); // ƒ fn () {}
console.log(a); // ReferenceError: a is not defined
function fn () {
console.log(a);
var a = 2
}
示例2的例子说明:
- 每个作用域都会发生变量提升;
- 变量提升只发生在当前作用域,而不是会提升到整个程序最上方。
示例3
console.log(fn()); // l am is fn
console.log(fn2()) // TypeError: fn2 is not a function
function fn () {
console.log('l am is fn');
}
var fn2 = function () {
console.log('l am is fn2');
}
示例3的例子说明:
- fn2被提升并分配给所在作用域,但由于fn2是undefined,对undefined进行函数调用会抛出TypeError异常。
示例4
console.log(fn()); // l am is fn
console.log(fn3()); // ReferenceError: fn3 is not defined
function fn () {
console.log('l am is fn');
};
var fn2 = function fn3() {
console.log('l am is fn2');
};
是不是对fn3()
的表现和异常提示有些奇怪,如果你发现了这个问题,说明离真相不远了。
示例4的例子说明:
- 即使是具名函数,名称标识符(例如fn3)在赋值之前也无法在所在作用域中访问。
- 回到示例1中所诉,赋值声明会被留在原地等待执行阶段执行。换句话说就是,具名函数在当前(全局)作用域中进行变量提升时,赋值操作不会进行提升。
- 上面的例子经过提升实际会编译成下面这样:
var fn2 console.log(fn()); // l am is fn console.log(fn3()); // ReferenceError: fn3 is not defined function fn () { console.log('l am is fn'); }; fn2 = function () { var fn3 = ... ... };
示例5
var fn1
fn1() // l am is fn1 statement
function fn1 () {
console.log('l am is fn1 statement');
}
fn1 = function () {
console.log('l am is fn1 expression');
}
fn1() // l am is fn1 expression
示例5说明:
- 函数优先。函数会首先被提升,然后才是普通变量;
- var fn1的声明在function fn1之前,但是被忽略了。
- 尽管如此,后面的声明仍然可以覆盖前面的(不建议这么写)。
示例6
fn() // TypeError: fn is not a function
var a = true
fn() // 3
if (a) {
var fn1 = 2
function fn () { console.log(1); }
fn () // 1
} else {
function fn () { console.log(2); }
}
// function fn () {
// console.log(3);
// }
fn () // 1
示例6的例子说明:
- 普通块内的函数声明会被提升到所在作用域的顶部;
- 在普通块外部的同名函数声明会早于块内的同名函数声明,因此会导致,块内的函数覆盖块外的函数。
总结:
- 变量和函数声明赋值操作,在js引擎看来是分为两个阶段的,一是编译阶段,提升就是发生在这个阶段,二是执行阶段。
- 无论变量和函数声明在什么位置,都会被先“移动”到所在作用域的顶部,这过程称之为提升。
- 声明本身会提升,但包括函数表达式赋值在内的赋值操作不会被提升。
- 提升时的覆盖规则:
- 同名函数:后面的覆盖前面的;
- 同名变量:后面的会被忽略;
- 同名函数和变量:函数的提升会覆盖变量的提升。
- 避免重复声明,会导致很多不必要的问题。
ps:文章中若发现错误请帮博主指正,感谢~