[译]Javascript变量生命周期:为什么let不支持提升

303 阅读4分钟

虽然知道let不能提升作用域,但是不知其所以然。于是看看这文章吧。原文:dmitripavlutin.com/variables-l…

所谓提升是指将虚拟的将变量或者函数定义放在作用域的最开始阶段。通常用在变量var和函数声明funtion fun(){...}。 当let(const和class跟let相似的声明行为)被ES6所引入的时候,很多开发者包括我都是使用作用域提升的方式,在查阅了相关问题后,惊奇发现作用域提升并没有用在let初始化和可用性上。

Es2015为let提供了不同的并且改善的机制.它需要严格地变量声明操作。你不能在声明前使用,提供了一个更好的代码质量。具体的细节:

1. 容易出错的var提升

随意的var和函数声明,经典的提升:

// var hoisting变量提升
num
var num;
num = 10;
num;
// function hoisting
getPi; // 
getPi();//3.14
function getPi() {
    return 3.14;
}

上述代码混乱。

2. 理解变量的生命周期

在引擎中的变量生命周期包含如下阶段:

  1. 声明是在作用域中注册变量
  2. 初始化是分配内存和创建绑定关系,在这个阶段变量自动设置为undefined
  3. 赋值阶段是分配一个值给这个初始化过程 变量有声明但为初始化的阶段

var变量的生命周期

熟悉了上述的生命周期阶段,用他们来描述一下JS引擎是如何处理var变量的 !()[dmitripavlutin.com/static/112c…] 假定如下的场景:当Js遇到一个函数域包含着一个var variable的声明。变量通过声明和初始化阶段的(step1),之后在未被赋值之前,这个变量将会有一个undefined的值并且可以被使用。

在赋值阶段variable='value',变量接收到了它的初始值(step2).

严格提升包含在函数的开始阶段进行变量声明和初始化。在声明和初始化之间没有界限。 如下例子创建一个函数域包含一个变量:

function multiplyByTen(number) {
    console.log(ten);
    var ten;
    ten = 10;
    console.log(ten);
    return number * ten;
}

4.函数声明生命周期

声明、初始化和赋值一起发生在函数作用域的开头。funName()可以被包含在任何的域中,不依赖于声明的位置。

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

5. let生命周期

let变量与var的主要不同在于声明和初始化是分开的。

现在让我们研究一个场景,当解释器进入一个包含let变量语句的块范围时。变量立即通过声明阶段,在作用域中注册其名称(步骤1)。然后解释器继续逐行解析块语句。 如果在此阶段尝试访问变量,JavaScript将抛出ReferenceError: variable未定义。这是因为变量状态未初始化。变量在暂时性死区。 当解析器到达let variable时,开始初始化阶段(step2).变量状态被初始化并且可以访问到undefined。变量退出暂时性死区(temporal dead zone)。 然后当赋值语句出现当时候,赋值阶段也就通过了。 如果Javascript遇到let variable = 'value',初始化和赋值阶段将会发生在单个状态中。

let condition = true;
if (condition) {
  // console.log(number); // => Throws ReferenceError
  let number;
  console.log(number); // => undefined
  number = 5;
  console.log(number); // => 5
}

当Javascript进入if(condition){...}块域的时候,number立即经历声明阶段。由于number没有初始化状态且处在一个暂时性死区。尝试区访问变量时将会抛ReferenceError: number is not defined.

为什么在let的生命周期里提升不是有效的

如上所述,提升是作用域顶部变量的耦合声明和初始化。但是,let的生命周期中声明和初始化是解耦,解耦导致了提升失效。这两个阶段之间的间隔创建了一个暂时性死区,在这里变量不能被访问。

结论

Because the declaration and initialization phases are decoupled, hoisting is not valid for a let variable (including for const and class). Before initialization, the variable is in temporal dead zone and is not accessible.