知道临时死区你才能更好的使用 JS 变量

开发 前端
TDZ 是影响 const、let 和 class 语句可用性的重要概念。它不允许在声明之前使用变量。相反,可以在声明之前使用 var 变量时,var 变量会继承较旧的行为,应该避免这样做。

首先,来个一个简单的问题。下列哪段代码会产生错误:

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

  1. new Car('red'); // 是否会报错? 
  2.  
  3. class Car { 
  4.   constructor(color) { 
  5.     this.color = color; 
  6.   } 

或者先于函数定义之前调用函数:

  1. greet('World'); // 是否会报错? 
  2.  
  3. function greet(who) { 
  4.   return `Hello, ${who}!`; 
  5. }    

正确的答案是:第一个代码片段会报 ReferenceError: Cannot access 'Car' before initialization 错误。第二个代码正常运行。

如果你的答案与上述不同,或者你在不知道这背后的原理是什么而进行了猜测,那么你需要掌握临时死区(TDZ)的知识。

TDZ 管理 let、const 和 class 语法的可用性。变量在 JS 中的工作方式非常重要。

1. 什么是临时死区

咱们先从一个简单的 const 变量声明开始。首先声明并初始化变量,然后访问它,一切正常运行:

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

那如果在 声明之前访问 white 变量,会怎么样?

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

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

在 TDZ 中访问 white 后,JS抛出ReferenceError: Cannot access 'white' before initialization

JS

临时死区语义禁止在变量声明之前访问它。它加强了顺序:在声明之前不要使用任何东西。

2. 受 TDZ 影响的声明

来看看受 TDZ 影响的声明。

(1) const变量

如前所述,const 变量位于声明和初始化行之前的 TDZ 中:

  1. // 无法工作 
  2. pi; // throws `ReferenceError` 
  3.  
  4. const pi = 3.14; 

咱们必须在声明之后使用 const 变量:

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

(2) let 变量

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

  1. // 无法工作 
  2. count; // throws `ReferenceError` 
  3.  
  4. let count; 
  5.  
  6. count = 10

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

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

(3) class 的声明

正如在介绍中看到的,在定义 class 之前不能使用它:

  1. // 无法工作 
  2. const myNissan = new Car('red'); // throws `ReferenceError` 
  3.  
  4. class Car { 
  5.   constructor(color) { 
  6.     this.color = color; 
  7.   } 

(4) 构造函数内部的 super()

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

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

在构造 constructor() 中,在调用super()之前不能使用 this。

TDZ 建议调用父构造函数来初始化实例。这样做之后,实例就准备好了,就可以在子构造函数中进行调整。

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

(5) 默认函数参数

默认参数存在于一个中间作用域中,与全局作用域和函数作用域分离。默认参数也遵循 TDZ 限制。

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

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

确保在声明和初始化之后使用默认参数。咱们可以使用一个特殊的变量 init,该变量在使用前已初始化:

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

3. var, function, import 语句

与上述陈述相反,var 和 function 定义不受 TDZ 的影响。它们被提升到当前的作用域顶部。

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

  1. // 正常运行, 但不要这样做! 
  2. value; // => undefined 
  3.  
  4. var value; 

但是,可以根据函数的定义位置来使用它:

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

通常,咱们一般对函数的实现不太感兴趣,而只是想调用它。因此,有时在定义函数之前先调用该函数是有意义的。

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

  1. // 正常工作 
  2. myFunction(); 
  3.  
  4. import { myFunction } from './myModule'; 

当然,建议将 import 写在文件开头,以便读写方法。

4. TDZ 中的 typeof 行为

typeof 操作符用于确定是否在当前作用域内定义了变量。

例如,未定义变量 notDefined。对该变量应用 typeof 操作符不会引发错误:

  1. typeof notDefined; // => 'undefined' 

因为变量没有定义,所以 typeof notDefined 的值为 undefined。

但是 typeof 操作符在与临时死区中的变量一起使用时具有不同的行为。在本例中,JS 抛出一个错误:

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

此引用错误背后的原因是您可以静态地(仅通过查看代码)确定已经定义了该变量。

5. TDZ 在当前作用域内采取行动

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

来看看例子:

  1. function doSomething(someVal) { 
  2.   // 函数作用域 
  3.   typeof variable; // => undefined 
  4.   if (someVal) { 
  5.     // 内部块使用域 
  6.     typeof variable; // throws `ReferenceError` 
  7.     let variable; 
  8.   } 
  9. doSomething(true); 

有 2 个作用域:

  • 函数作用域
  • 定义 let 变量的内部块作用域

在函数作用域中,typeof variable 的计算结果为 undefined。在这里,let 变量语句的 TDZ 没有作用。

在内部作用域中,typeof variable 语句在声明之前使用一个变量,抛出一个错误。ReferenceError:在初始化之前不能访问‘variable’,TDZ 只存在于这个内部作用域内。

6. 总结

TDZ 是影响 const、let 和 class 语句可用性的重要概念。它不允许在声明之前使用变量。

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

在我看来,TDZ是语言规范中良好的编码实践之一。

责任编辑:赵宁宁 来源: 大迁世界
相关推荐

2020-11-19 07:49:24

JS变量作用域

2020-10-08 18:58:46

条件变量开发线程

2020-07-20 12:43:31

Go变量命名

2024-11-08 08:42:08

内省机制JavaBean描述器

2022-03-10 08:25:27

JavaScrip变量作用域

2021-12-29 11:38:59

JS前端沙箱

2010-03-15 14:47:19

Python内置对象

2021-11-16 08:51:29

Node JavaScript变量类型

2021-03-04 23:11:59

环境变量Python

2023-01-04 08:17:21

Typescript技巧欺骗性

2024-02-05 11:55:41

Next.js开发URL

2011-12-09 11:16:48

Node.js

2020-11-23 11:04:17

Redis协议缓存

2020-08-11 11:20:49

Linux命令使用技巧

2019-05-30 22:27:51

云计算成本云服务

2021-03-17 08:00:59

JS语言Javascript

2020-09-23 13:44:26

分类变量独热编码编码

2020-11-26 07:48:24

Shell 脚本内置

2012-09-25 13:32:31

大数据Hadoop

2023-10-30 11:53:37

继承JS父类
点赞
收藏

51CTO技术栈公众号