资讯专栏INFORMATION COLUMN

JS变量生命周期:为什么 let 没有被提升

Steven / 3183人阅读

摘要:请注意,就变量生命周期而言,声明阶段与变量声明是不同的概念。提升在生命周期中无效的原因如上所述,提升是变量在作用域顶部的耦合声明和初始化阶段。然而,生命周期分离声明和初始化阶段。解耦消除了的提升期限。

为了保证的可读性,本文采用意译而非直译。

提升是将变量或函数定义移动到作用域头部的过程,通常是 var 声明的变量和函数声明function fun() {...}

当 ES6 引入let(以及与let类似声明的constclass)声明时,许多开发人员都使用提升定义来描述如何访问变量。但是在对这个问题进行了更多的探讨之后,令我惊讶的是提升并不是描述let变量的初始化和可用性的正确术语。

ES6 为let提供了一个不同的和改进的机制。它要求更严格的变量声明,在定义之前不能使用,从而提高代码质量。

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

1. 容易出错的 var 提升

有时候我们会在zuo内作用域内看到一个奇怪的变量var varname和函数函数function funName() {...} 声明:

// var hoisting
num;     // => undefined
var num;
num = 10;
num;     // => 10
// function hoisting
getPi;   // => function getPi() {...}
getPi(); // => 3.14
function getPi() {
  return 3.14;
}

变量num在声明var num之前被访问,因此它被赋值为undefinedfucntion getPi(){…}在文件末尾定义。但是,可以在声明getPi()之前调用该函数,因为它被提升到作用域的顶部。

事实证明,先使用然后声明变量或函数的可能性会造成混淆。假设您滚动一个大文件,突然看到一个未声明的变量,它到底是如何出现在这里的,以及它在哪里定义的?

当然,一个熟练的JavaScript开发人员不会这样编写代码。但是在成千上万的JavaScript中,GitHub repos是很有可能处理这样的代码的。

即使查看上面给出的代码示例,也很难理解代码中的声明流。

当然,首先要声明再使用。let 鼓励咱们使用这种方法处理变量。

2. 理解背后原理:变量生命周期

当引擎处理变量时,它们的生命周期由以下阶段组成:

声明阶段(Declaration phase)是在作用域中注册一个变量。

初始化阶段(Initialization phase)是分配内存并为作用域中的变量创建绑定。 在此步骤中,变量将使用undefined自动初始化。

赋值阶段(Assignment phase)是为初始化的变量赋值。

变量在通过声明阶段时尚未初始化状态,但未达到初始化状态。

请注意,就变量生命周期而言,声明阶段与变量声明是不同的概念。 简而言之,JS引擎在3个阶段处理变量声明:声明阶段,初始化阶段和赋值阶段。

3.var 变量的生命周期

熟悉生命周期阶段之后,让我们使用它们来描述JS引擎如何处理var变量。

假设JS遇到一个函数作用域,其中包含var变量语句。变量在执行任何语句之前通过声明阶段,并立即通过作用域开始处的初始化阶段(步骤1)。函数作用域中var变量语句的位置不影响声明和初始化阶段。

在声明和初始化之后,但在赋值阶段之前,变量具有undefined 的值,并且已经可以使用。

在赋值阶段variable = "value" 时,变量接收它的初值(步骤2)。

严格意义的提升是指在函数作用域的开始处声明并初始化一个变量。声明阶段和初始化阶段之间没有差别。

让我们来研究一个例子。下面的代码创建了一个包含var语句的函数作用域

function multiplyByTen(number) {
  console.log(ten); // => undefined
  var ten;
  ten = 10;
  console.log(ten); // => 10
  return number * ten;
}
multiplyByTen(4); // => 40

开始执行multipleByTen(4)并进入函数作用域时,变量ten在第一个语句之前通过声明和初始化步骤。因此,当调用console.log(ten)时,打印undefined。语句ten = 10指定一个初值。赋值之后,console.log(ten) 将正确地打印10

4. 函数声明生命周期

在函数声明语句function funName() {...}的情况下,它比变量声明生命周期更简单。

声明、初始化和赋值阶段同时发生在封闭函数作用域的开头(只有一步)。可以在作用域的任何位置调用funName(),而不依赖于声明语句的位置(甚至可以在末尾调用)。

下面的代码示例演示了函数提升:

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

当执行sumArray([5,10,8])时,它进入sumArray函数作用域。在这个作用域内,在任何语句执行之前,sum都会通过所有三个阶段:声明、初始化和赋值。这样,array.reduce(sum)甚至可以在它的声明语句sum(a, b){…}之前使用sum

5. let 变量的生命周期

let 变量的处理方式与var不同,主要区别在于声明和初始化阶段是分开的

现在来看看一个场景,当解释器进入一个包含let变量语句的块作用域时。变量立即通过声明阶段,在作用域中注册其名称(步骤1)。

然后解释器继续逐行解析块语句。

如果在此阶段尝试访问变量,JS 将抛出 ReferenceError: variable is not defined。这是因为变量状态未初始化,变量位于暂时死区 temporal dead zone

当解释器执行到语句let variable时,传递初始化阶段(步骤2)。变量退出暂时死区。

接着,当赋值语句variable = "value"出现时,将传递赋值阶段(步骤3)。

如果JS 遇到let variable = "value",那么初始化和赋值将在一条语句中发生。

让我们看一个例子,在块作用域中用 let 声明变量 number

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

当 JS 进入if (condition) {...} 块作用域,number立即通过声明阶段。

由于number已经处于单一化状态,并且处于的暂时死区,因此访问该变量将引发ReferenceError: number is not defined。接着,语句let number进行初始化。现在可以访问变量,但是它的值是undefined

constclass 类型与let具有相同的生命周期,只是分配只能发生一次。

5.1 提升在let生命周期中无效的原因

如上所述,提升是变量在作用域顶部的耦合声明和初始化阶段。然而,let生命周期分离声明和初始化阶段。解耦消除了let的提升期限。

这两个阶段之间的间隙产生了暂时死区,在这里变量不能被访问。

总结

使用var声明变量很容易出错。在此基础上,ES6 引入了let。它使用一种改进的算法来声明变量,并附加了块作用域。

由于声明和初始化阶段是解耦的,提升对于let变量(包括constclass)无效。在初始化之前,变量处于暂时死区,不能访问。

为了保持变量声明的流畅性,建议使用以下技巧

声明、初始化然后使用变量,这个流程是正确的,易于遵循。

尽量隐藏变量。公开的变量越少,代码就越模块化。

番外 如何理解 let x = x 报错之后,再次 let x 依然会报错?

这个问题说明:如果 let x 的初始化过程失败了,那么

x 变量就将永远处于 created 状态。

你无法再次对 x 进行初始化(初始化只有一次机会,而那次机会你失败了)。

由于 x 无法被初始化,所以 x 永远处在暂时死区

有人会觉得 JS 坑,怎么能出现这种情况;其实问题不大,因为此时代码已经报错了,后面的代码想执行也没机会。

参考:

我用了两个月的时间才理解 let

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75453.html

相关文章

  • JS变量生命周期:什么 let 没有提升

    摘要:请注意,就变量生命周期而言,声明阶段与变量声明是不同的概念。提升在生命周期中无效的原因如上所述,提升是变量在作用域顶部的耦合声明和初始化阶段。然而,生命周期分离声明和初始化阶段。解耦消除了的提升期限。 为了保证的可读性,本文采用意译而非直译。 提升是将变量或函数定义移动到作用域头部的过程,通常是 var 声明的变量和函数声明function fun() {...}。 当 ES6 引入l...

    hoohack 评论0 收藏0
  • ES6 变量作用域与提升变量生命周期详解

    摘要:不同的是函数体并不会再被提升至函数作用域头部,而仅会被提升到块级作用域头部避免全局变量在计算机编程中,全局变量指的是在所有作用域中都能访问的变量。 ES6 变量作用域与提升:变量的生命周期详解从属于笔者的现代 JavaScript 开发:语法基础与实践技巧系列文章。本文详细讨论了 JavaScript 中作用域、执行上下文、不同作用域下变量提升与函数提升的表现、顶层对象以及如何避免创建...

    lmxdawn 评论0 收藏0
  • JavaScript 变量提升

    摘要:生命周期假设这样一个场景当解释器刚进入一个包含的作用域时,则在任何语句执行之前,变量就已完成了声明阶段和初始化阶段,且值为。当解释器执行完,变量就已完成了初始化阶段,离开了临时死区,并具有的值。 变量提升是一个将变量声明或者函数声明提升到作用域起始处的过程。在本篇博文中,我们一起深入了解这个过程的更多细节。 变量的生命周期 当引擎使用变量时,它们的生命周期包含以下阶段: 声明阶段,...

    li21 评论0 收藏0
  • 译:用let 和 const 来指导JavaScript 的变量提升

    摘要:最近在上看到一篇关于变量提升的文章,原文在此。对于刚入门的开发者时常难以理解变量方法提升的独特行为。接下来我们要谈论,,声明,那么先了解变量提升就显得更为重要了。在进入作用域和不能访问的这段时间,我们称为暂时性死区。 showImg(https://segmentfault.com/img/bV0Nsd?w=800&h=450); 最近在Medium上看到一篇关于变量提升的文章,原文在...

    sanyang 评论0 收藏0
  • 高级前端面试题大汇总(只有试题,没有答案)

    摘要:面试题来源于网络,看一下高级前端的面试题,可以知道自己和高级前端的差距。 面试题来源于网络,看一下高级前端的面试题,可以知道自己和高级前端的差距。有些面试题会重复。 使用过的koa2中间件 koa-body原理 介绍自己写过的中间件 有没有涉及到Cluster 介绍pm2 master挂了的话pm2怎么处理 如何和MySQL进行通信 React声明周期及自己的理解 如何...

    kviccn 评论0 收藏0

发表评论

0条评论

Steven

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<