【JS】变量提升

133 阅读4分钟

什么是变量提升?

变量提升-Hoisting是JavaScript中的一种行为,指的是变量和函数的声明在编译阶段被“提升”到它们所在作用域的顶部,即使它们在代码中实际出现的位置是在后面。变量提升适用于使用 var 声明的变量和所有的函数声明,而不包括 let 和 const 声明的变量。赋值部分则不会被提升,赋值操作依然发生在代码执行的地方。

  • 函数声明:函数的整个声明和定义都会被提升。
  • var 声明:只提升声明,赋值保持在原来的位置。
  • let 和 const:不会被提升(即使在它们的作用域中,它们也会存在“暂时性死区”(Temporal Dead Zone, TDZ),只有在它们的声明语句之后才能被访问)。

1.带var 以及 不带var 的区别

console.log(a, b)  // undefined undefined
var a = 1, b = 10
function foo() {
    // 2
    console.log(a, b)  // undefined 10
    // 3
    var a = b = 20
    console.log(a, b)  // 20 20
}
foo()
console.log(a, b)  // 1 20

foo函数代码,等效于下面的

function foo() {
    var a;  // 变量提升,a 是局部变量,初始 undefined
    console.log(a, b);  // 2. undefined 10
    a = b = 20;  // 关键行!
    console.log(a, b);  // 3. 20 20
}

首先对上述代码进行分析
第2行代码,首先对a以及b进行了声明以及定义,此时ab都是var进行声明的,且在全局作用域下
第7行代码,这里在函数内部对a以及b进行了声明以及定义,此时a是局部的var进行声明的,在函数局部进行变量提升,b隐式全局变量
对于上面的两行代码有了了解之后,对于输出就应该没什么问题了

第1行,由于进行了变量提升,所以输出都是 undefined
第5行,在局部,变量a局部变量,且需要进行变量提升,所以第5行a = undefined,b此时是全局变量的引用,所以b = 10
第8行,由于第7行进行了赋值,此时按照逻辑直接输出 20 20
第11行,此时a输出全局变量a = 1,,也就是第2行的定义,b此时已经被函数内部的隐式全局变量b = 20覆盖,此时b = 20

sample1
console.log(a, b)   // undefined undefined
var a = 1, b = 10
function foo() {    
    console.log(a, b)   // 1 10
}
foo()
console.log(a, b)   // 1 10

由于在foo内部没有任何对于a以及b的声明,此时就向上一级进行查询ab的值

sample2
function foo() {
    console.log(a)
    a = 1;
    b = 10
    console.log('b' in window)
    console.log(a, b)
}
foo()

对于上述代码,直接报错
由于 JavaScript 的作用域链变量提升规则,如果没有在函数内用 varlet 或 const 声明,a 将会被看作是一个全局变量。由于在全局内,未对a进行定义,所以报错

sample3
function foo() {
    // console.log(a)
    a = 1;
    b = 10
    console.log('b' in window)  // true
    console.log(a, b)
}
foo()

如果对上述代码的第二行注释,此时会将a以及b都隐式的声明为全局变量,所以此时第五行输出为 true

sample4
fn();
// console.log(v1);
console.log(v2);
console.log(v3);
function fn() {
    var v1 = v2 = v3 = 1;
    console.log(v1);
    console.log(v2);
    console.log(v3);
}

对于上述代码,输出为 5 个 1
如果不注释第二行,此时的输出呢?
此时,v1是在函数内局部用var定义,在全局作用域下是没有v1的,此时直接输出3个1之后然后进行报错

sample5
if (true) {
  var blockVar = "var in block";
  let blockLet = "let in block";
  const blockConst = "const in block";
}
console.log(blockVar); // 输出: "var in block"(因为 var 提升到全局)
console.log(blockLet);   // 报错:blockLet is not defined
console.log(blockConst); // 报错:blockConst is not defined

function fn() {
  var funcVar = "var in function";
  let funcLet = "let in function";
  const funcConst = "const in function";
}
fn();
console.log(funcVar);   // 报错:funcVar is not defined
console.log(funcLet);   // 报错:funcLet is not defined
console.log(funcConst); // 报错:funcConst is not defined

作用域
上面的一个是块级作用域,所以存在变量提升,var的声明下可以正常输出,let/const就不能行。
另外一个就是函数作用域,这里就不存在变量提升,function会隔离所有的变量声明。

2.等号左边的变量提升

console.log(fn) // undefined
fn()    // 报错
var fn = function () {
    console.log(111)
}
fn()

由于是var进行定义的,此时进行变量提升,第一行fn输出为undefined,此时第二行就会报错,因为是undefined(),此时未对这个函数进行定义,所以报错
如果主事上述第二行代码,可以正常运行,在进行变量提升之后,然后在第三行进行赋值,此时可以正常运转