专栏首页京程一灯别在不知道临时死区的情况下使用 JavaScript 变量[每日前端夜话0xD2]

别在不知道临时死区的情况下使用 JavaScript 变量[每日前端夜话0xD2]

作者:Dmitri Pavlutin

翻译:疯狂的技术宅

来源:dmitripavlutin

我问一个简单的问题。以下哪个代码片段将会产生错误?

第一个创建实例,然后定义所用的类:

1new Car('red'); // Does it work?
2
3class Car {
4  constructor(color) {
5    this.color = color;
6  }
7}

第二个先调用然后定义函数:

1greet('World'); // Does it work?
2
3function greet(who) {
4  return `Hello, ${who}!`;
5}

正确答案:第一个代码段(带有类)将生成 ReferenceError。第二个工作正常。

如果你的答案与上述不同,或者在不知道底层发生了什么的情况下进行了猜测,那么你需要掌握临时死区(TDZ)。

TDZ 管理 letconstclass 语句的可用性。对于变量在 JavaScript 中的工作方式非常重要。

1.什么是临时死区Temporal Dead Zone

让我们从一个简单的 const 变量声明开始。如果首先声明并初始化变量,然后访问它,那么一切都会按预期进行:

1const white = '#FFFFFF';
2white; // => '#FFFFFF'

现在让我们试着在声明之前访问 white 变量:

1white; // throws `ReferenceError`
2const white = '#FFFFFF';
3white;

在到 const white = '#FFFFFF' 语句的代码行之前,变量 white 位于时间死区中。

在 TDZ 中访问了 white 之后,JavaScript 会抛出 ReferenceError: Cannot access 'white' before initialization

JavaScript中的临时死区

TDZ(Temporal Dead Zone)语义禁止在声明变量之前访问变量。它强制执行纪律:在声明之前不要使用任何东西

2. 受 TDZ 影响的语句

让我们看看受 TDZ 影响的语句。

2.1 const 变量

正如你已经看到的,const 变量在 TDZ 中声明和初始化行之前:

1// Does not work!
2pi; // throws `ReferenceError`
3
4const pi = 3.14;

你必须在声明后使用 const 变量:

1const pi = 3.14;
2
3// Works!
4pi; // => 3.14

2.2 let 变量

在声明行之前,let 声明语句也会受到 TDZ 的影响:

1// Does not work!
2count; // throws `ReferenceError`
3
4let count;
5
6count = 10;

同样,仅在声明后使用 let 变量:

1let count;
2
3// Works!
4count; // => undefined
5
6count = 10;
7
8// Works!
9count; // => 10

2.3 class 语句

从简介中可以看出,在定义类之前不能使用它:

1// Does not work!
2const myNissan = new Car('red'); // throws `ReferenceError`
3
4class Car {
5  constructor(color) {
6    this.color = color;
7  }
8}

为了使它起作用,请在定义后保留类用法:

1class Car {
2  constructor(color) {
3    this.color = color;
4  }
5}
6
7// Works!
8const myNissan = new Car('red');
9myNissan.color; // => 'red'

2.4 constructor()内部的 super()

如果扩展父类,则在构造函数内部调用 super() 之前,this 绑定位于 TDZ 中:

1class MuscleCar extends Car {
2  constructor(color, power) {
3    this.power = power;
4    super(color);
5  }
6}
7
8// Does not work!
9const myCar = new MuscleCar('blue', '300HP'); // `ReferenceError`

constructor() 内部,this 在调用 super() 之前不能使用。

TDZ 建议调用父构造函数来初始化实例。完成之后,实例已准备就绪,你可以在子构造函数中进行调整。

 1class MuscleCar extends Car {
 2  constructor(color, power) {
 3    super(color);
 4    this.power = power;
 5  }
 6}
 7
 8// Works!
 9const myCar = new MuscleCar('blue', '300HP');
10myCar.power; // => '300HP'

2.5 默认函数参数

默认参数存在于 intermidiate 作用域内,与全局作用域和函数作用域分开。默认参数还遵循 TDZ 限制:

1const a = 2;
2function square(a = a) {
3  return a * a;
4}
5// Does not work!
6square(); // throws `ReferenceError`

在声明前,在表达式 a = a 的右侧使用参数 a。这会产生关于 a 的引用错误。

要确保在声明和初始化之后使用默认参数。让我们使用特殊的变量 init ,该变量在使用前已初始化:

1const init = 2;
2function square(a = init) {
3  return a * a;
4}
5// Works!
6square(); // => 4

3. varfunctionimport 语句

与前面相反,varfunction 的定义不受 TDZ 的影响。它们在当前作用域内被提升。

如果在声明之前访问 var 变量,则只会得到 undefined

1// Works, but don't do this!
2value; // => undefined
3
4var value;

However, a function can be used regarding where it is defined: 但是,可以使用函数定义其位置:

1// Works!
2greet('World'); // => 'Hello, World!'
3
4function greet(who) {
5  return `Hello, ${who}!`;
6}
7
8// Works!
9greet('Earth'); // => 'Hello, Earth!'

通常来说你对函数的实现不太感兴趣,而只是想调用它。所以有时在定义函数之前先调用该函数是有意义的。

有趣的是, import 模块也被提升:

1// Works!
2myFunction();
3
4import { myFunction } from './myModule';

import 时,在 JavaScript 文件的开头加载模块的依赖项是一个好的做法。

4. TDZ 中的 typeof 行为

typeof 运算符可用于确定变量是否在当前作用域内定义。

例如,变量 notDefined 未定义,在这个变量上应用 typeof 运算符不会引发错误:

1typeof notDefined; // => 'undefined'

由于未定义变量,因此 typeof notDefined 的值为 undefined

但是当与临时死区中的变量一起使用时,typeof 运算符有着不同的行为。在这种情况下,JavaScript 会报错:

1typeof variable; // throws `ReferenceError`
2
3let variable;

这个引用错误背后的原因是,你可以静态地(仅通过查看代码即可)确定已经定义了variable

5. TDZ 在当前作用域内运行

临时死区会在存在声明语句的作用域内影响变量。

JavaScript中的临时死区限制

让我们来看一个例子:

 1function doSomething(someVal) {
 2  // Function scope
 3  typeof variable; // => undefined
 4  if (someVal) {
 5    // Inner block scope
 6    typeof variable; // throws `ReferenceError`
 7    let variable;
 8  }
 9}
10doSomething(true);

There are 2 scopes: 有 2 个作用域:

  1. 函数作用域
  2. 定义 let 变量的内部块作用域

在函数作用域内,typeof variable 仅计算为 undefined。在这里, let variable 语句的 TDZ 无效。

在内部作用域中,在声明之前使用变量的 typeof variable 语句引发错误ReferenceError: Cannot access 'variable' before initialization。TDZ 仅存在于此内部作用域内。

6. 结论

TDZ 是一个重要概念,会影响 constletclass 语句的可用性。不允许在声明前使用变量。

当你可以在声明之前使用 var 变量时,它们会继承旧的行为。你应该避免这样做。

在我看来,当把良好的编码实践进入语言规范时,TDZ 就是其中的一个好东西。

原文:https://dmitripavlutin.com/javascript-variables-and-temporal-dead-zone/

本文分享自微信公众号 - 前端先锋(jingchengyideng)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 不仅会用@Async,我把源码也梳理了一遍(中)

    好了,距离上次发表《 不仅会用@Async,我把源码也梳理了一遍(上)》已经好几天了,上一篇文章中我们介绍了@EnableAsync+@Async的简单用法,今...

    java思维导图
  • 【小程序】359- 小程序运行机制

    前端的框架太多让人眼花缭乱,很多相似的地方,优秀的地方大家都会借鉴,同时又会有各自的一些特点。小程序也好,其他框架也好,理解他们的设计缘由、实现原理,还是能学到...

    pingan8787
  • 【带着canvas去流浪(14)】Three.js中凹浮雕模型的生成方式

    浮雕模型,简单地说就是在木板上刻字时所形成的效果,如果把字的部分都剔除掉,就得到一个凹浮雕模型,如果把字以外的部分都剔除掉,就得到一个凸浮雕模型。本文分别对利用...

    大史不说话
  • [搞懂Java集合类7]Java集合类细节精讲

    今天我们来探索一下Java集合类中的一些技术细节。主要是对一些比较容易被遗漏和误解的知识点做一些讲解和补充。可能不全面,还请谅解。

    Java技术江湖
  • TypeScript 疑难杂症

    作者:阿伟 - 身在高楼心在北大荒,我就这副死样~https://zhuanlan.zhihu.com/p/82459341

    ConardLi
  • ​AQS中的公平锁与非公平锁,Condtion

    一行一行源码分析清楚 AbstractQueuedSynchronizer (二)

    Java技术江湖
  • AQS共享模式与并发工具类的实现

    一行一行源码分析清楚 AbstractQueuedSynchronizer (三)

    Java技术江湖
  • Elasticsearch源码解析高并发写入优化

    导语:在腾讯金融科技数据应用部的全民 BI 项目里,我们每天面对超过 10 亿级的数据写入,提高 ES 写入性能迫在眉睫,在最近的一次优化中,有幸参与到了 El...

    王炸
  • torch、(二) Generators

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    于小勇
  • 和微信公众号编辑器战斗的日子

    公元 2019 年,微信公众号排版能力孱弱,始终为运营者所诟病,秀米、135 编辑器等工具割据一方。

    灵魂画师牧码

扫码关注云+社区

领取腾讯云代金券