对于let 是否存在变量提高的思考?

面试的时候,咱们常常会被问到let 、const 和 var 之间的区别,个人回答无疑老是那几点:面试

  • 变量提高bash

    var会进行变量提高,let和const不会进行提高函数

  • 暂存死区学习

    由于var会进行变量提高,因此能够在声明以前访问,不会造成暂存死区。ui

    let 和const 不会进行变量提高,在声明以前不能使用,造成暂存死区spa

  • 重复声明code

    var能够进行重复声明,可是let和const不能进行重复声明cdn

  • 块做用域对象

    var不会造成块做用域,let和const能够造成块做用域blog

  • 从新赋值

    var和let声明的变量能够从新赋值,const不能够。

    若是const 声明的变量存储的是引用地址, 是能够修改这个引用对应的对象的值的,可是这个变量不能被赋予其余值

每次都是这个样回答,面试官也没有说什么。

但有一次面试的时候,一面面试官问到我,let的所谓的暂时性死区怎么解释?let和const到底有没有变量提高?我没回答出来,而后面试官和我解释了一下,而且推荐我看一个大神的这篇文章:zhuanlan.zhihu.com/p/28140450

经过这篇文章,以及这篇文章所提到的文章得出的结论就是:

let 的「建立」过程被提高了,可是初始化没有提高。

接下来就讲为何?从变量的生命周期开始!

一、变量的生命周期

在这里我比较喜欢这篇文章:JavaScript Variables Lifecycle: Why let Is Not Hoisted

首先咱们来学习一下变量的生命周期:

当引擎使用变量时,它们的生命周期包括如下几个阶段:

  1. 声明阶段(Declaration phase)正在范围内注册变量。
  2. 初始化阶段(Initialization phase)是分配内存并为做用域中的变量建立绑定。在此步骤中,变量将使用进行自动初始化undefined。
  3. 分配阶段(Assignment phase)是为初始化变量分配一个值。

变量在经过声明阶段时已处于统一状态,但还没有达到初始化状态。

可是要注意的是,就变量生命周期而言,声明阶段与通常而言的变量声明是不一样的术语。简而言之,引擎在三个阶段处理变量声明:声明阶段,初始化阶段和赋值阶段。

1.1 var变量的生命周期

var声明的变量在所在的做用域的开始处声明和初始化变量。声明和初始化阶段之间没有差距。

function var_variable(){
  console.log('在声明阶段以前',one_variable);
  var one_variable;
  console.log('在未赋值以前',one_variable);
  one_variable = 'be assigned';
  console.log('赋值以后', one_variable);
}
//console.log('在函数做用域外是否能够访问到var声明的变量', one_variable);//one_variable is not defined
var_variable();
复制代码

运行结果:

在未声明阶段以前 undefined
在未赋值以前 undefined
赋值以后 be assigned
复制代码

在执行任何语句以前,变量在做用域的开头经过声明阶段并当即初始化阶段(上图步骤1)。 var variable语句在函数做用域中的位置不影响声明和初始化阶段。

在声明和初始化以后,可是在赋值阶段以前,该变量具备undefined值而且能够被使用。

在赋值阶段 variable = 'value',变量将接收其初始值(上图步骤2)。

1.2 函数声明生命周期

函数声明:function funName() {...}

function 声明会在代码执行以前就「建立、初始化并赋值」。

function sumArray(array) {
  return array.reduce(sum);
  function sum(a, b) {
    return a + b;
  }
}
sumArray([5, 10, 8]); // => 23
复制代码

执行JavaScript时sumArray([5, 10, 8]),它将进入sumArray函数范围。在此范围内,紧接着执行任何语句以前,sum将经过全部三个阶段:声明,初始化和赋值。 这种方式甚至array.reduce(sum)能够sum在其声明语句以前使用function sum(a, b) {...}。

1.3 let变量生命周期

let变量的处理方式不一样于var。主要区别是声明和初始化阶段是分开的。

let a = 1;
{
  // 初始化前没法访问“a”,在这里的a指的是下面的a,不是全局的a,此时a尚未被初始化,
  //因此在这里log会报错,由于在这里是暂时性死区
  // console.log(a);
  

  let a;
  //解释器进入包含let variable语句的块范围的状况。变量当即经过声明阶段,在范围内注册其名称,
  //而后解释器继续逐行解析块语句。
  
  console.log('########## - a', a);//初始化,对其进行访问的结果为undefined
  a='被从新赋值了'
  console.log('========= - a', a);

  console.log('########## - b', b);
  var b;
  b='b';
  console.log('========= - b', b);
}
console.log('全局的a',a);
console.log('全局的b', b);
复制代码

运行结果:

########## - a undefined
========= - a 被从新赋值了
########## - b undefined
========= - b b
全局的a 1
全局的b b
复制代码

我认为:在块级做用域中,从块级做用域中的第一行开始,到用let variable声明变量这一行以前,这一段区域是let的暂时性死区。

这个暂时性死区就是在上图的Uninitialized state阶段,若是在这一阶段的时候,访问这个变量就会报错。

在初始化阶段Initialized state以后就能够访问这个let变量了。

1.3.1 为何 Hosting 在生命周期中无效

如上所述,提高是变量在顶部的耦合声明和初始化。 可是let声明的变量,他的生命周期使声明和初始化阶段脱钩。解耦消除了 Hosting的术语let。 这两个阶段之间的间隙建立了临时死区,没法访问该变量。

总结

let 的「建立」过程被提高了,可是初始化没有提高。由于声明和初始化阶段是分离的,因此提高对于let变量(包括for constclass)无效。在初始化以前,该变量位于时间死区中而且不可访问。

在这里再提一下:

若是 let x的初始化过程失败了,那么 x 变量就将永远处声明(Declaration)状态。 你没法再次对x进行初始化(初始化只有一次机会,而那次机会你失败了)。 因为 x 没法被初始化,因此x永远处在暂时死区.

相关文章
相关标签/搜索