作者:Dmitri Pavlutin翻译:疯狂的技术宅
原文:https://dmitripavlutin.com/ja...
未经允许严禁转载
我问一个简单的问题。以下哪个代码片段将会产生错误?
第一个创建实例,然后定义所用的类:
new Car('red'); // Does it work?
class Car {
constructor(color) {
this.color = color;
}
}
第二个先调用然后定义函数:
greet('World'); // Does it work?
function greet(who) {
return `Hello, ${who}!`;
}
正确答案:第一个代码段(带有类)将生成 ReferenceError
。第二个工作正常。
如果你的答案与上述不同,或者在不知道底层发生了什么的情况下进行了猜测,那么你需要掌握临时死区(TDZ)。
TDZ 管理 let
,const
和 class
语句的可用性。对于变量在 JavaScript 中的工作方式非常重要。
1.什么是临时死区(Temporal Dead Zone)
让我们从一个简单的 const
变量声明开始。如果首先声明并初始化变量,然后访问它,那么一切都会按预期进行:
const white = '#FFFFFF';
white; // => '#FFFFFF'
现在让我们试着在声明之前访问 white
变量:
white; // throws `ReferenceError`
const white = '#FFFFFF';
white;
在到 const white = '#FFFFFF'
语句的代码行之前,变量 white
位于时间死区中。
在 TDZ 中访问了 white
之后,JavaScript 会抛出 ReferenceError: Cannot access 'white' before initialization
。
TDZ(Temporal Dead Zone)语义禁止在声明变量之前访问变量。它强制执行纪律:在声明之前不要使用任何东西。
2. 受 TDZ 影响的语句
让我们看看受 TDZ 影响的语句。
2.1 const 变量
正如你已经看到的,const
变量在 TDZ 中声明和初始化行之前:
// Does not work!
pi; // throws `ReferenceError`
const pi = 3.14;
你必须在声明后使用 const
变量:
const pi = 3.14;
// Works!
pi; // => 3.14
2.2 let 变量
在声明行之前,let
声明语句也会受到 TDZ 的影响:
// Does not work!
count; // throws `ReferenceError`
let count;
count = 10;
同样,仅在声明后使用 let
变量:
let count;
// Works!
count; // => undefined
count = 10;
// Works!
count; // => 10
2.3 class 语句
从简介中可以看出,在定义类之前不能使用它:
// Does not work!
const myNissan = new Car('red'); // throws `ReferenceError`
class Car {
constructor(color) {
this.color = color;
}
}
为了使它起作用,请在定义后保留类用法:
class Car {
constructor(color) {
this.color = color;
}
}
// Works!
const myNissan = new Car('red');
myNissan.color; // => 'red'
2.4 constructor()内部的 super()
如果扩展父类,则在构造函数内部调用 super()
之前,this
绑定位于 TDZ 中:
class MuscleCar extends Car {
constructor(color, power) {
this.power = power;
super(color);
}
}
// Does not work!
const myCar = new MuscleCar('blue', '300HP'); // `ReferenceError`
在constructor()
内部,this
在调用 super()
之前不能使用。
TDZ 建议调用父构造函数来初始化实例。完成之后,实例已准备就绪,你可以在子构造函数中进行调整。
class MuscleCar extends Car {
constructor(color, power) {
super(color);
this.power = power;
}
}
// Works!
const myCar = new MuscleCar('blue', '300HP');
myCar.power; // => '300HP'
2.5 默认函数参数
默认参数存在于 intermidiate 作用域内,与全局作用域和函数作用域分开。默认参数还遵循 TDZ 限制:
const a = 2;
function square(a = a) {
return a * a;
}
// Does not work!
square(); // throws `ReferenceError`
在声明前,在表达式 a = a
的右侧使用参数 a
。这会产生关于 a
的引用错误。
要确保在声明和初始化之后使用默认参数。让我们使用特殊的变量 init
,该变量在使用前已初始化:
const init = 2;
function square(a = init) {
return a * a;
}
// Works!
square(); // => 4
3. var,function,import 语句
与前面相反,var
和 function
的定义不受 TDZ 的影响。它们在当前作用域内被提升。
如果在声明之前访问 var
变量,则只会得到 undefined
:
// Works, but don't do this!
value; // => undefined
var value;
However, a function can be used regarding where it is defined:
但是,可以使用函数定义其位置:
// Works!
greet('World'); // => 'Hello, World!'
function greet(who) {
return `Hello, ${who}!`;
}
// Works!
greet('Earth'); // => 'Hello, Earth!'
通常来说你对函数的实现不太感兴趣,而只是想调用它。所以有时在定义函数之前先调用该函数是有意义的。
有趣的是, import
模块也被提升:
// Works!
myFunction();
import { myFunction } from './myModule';
import
时,在 JavaScript 文件的开头加载模块的依赖项是一个好的做法。
4. TDZ 中的 typeof 行为
typeof
运算符可用于确定变量是否在当前作用域内定义。
例如,变量 notDefined
未定义,在这个变量上应用 typeof
运算符不会引发错误:
typeof notDefined; // => 'undefined'
由于未定义变量,因此 typeof notDefined
的值为 undefined
。
但是当与临时死区中的变量一起使用时,typeof
运算符有着不同的行为。在这种情况下,JavaScript 会报错:
typeof variable; // throws `ReferenceError`
let variable;
这个引用错误背后的原因是,你可以静态地(仅通过查看代码即可)确定已经定义了variable
。
5. TDZ 在当前作用域内运行
临时死区会在存在声明语句的作用域内影响变量。
让我们来看一个例子:
function doSomething(someVal) {
// Function scope
typeof variable; // => undefined
if (someVal) {
// Inner block scope
typeof variable; // throws `ReferenceError`
let variable;
}
}
doSomething(true);
There are 2 scopes:
有 2 个作用域:
- 函数作用域
- 定义
let
变量的内部块作用域
在函数作用域内,typeof variable
仅计算为 undefined
。在这里, let variable
语句的 TDZ 无效。
在内部作用域中,在声明之前使用变量的 typeof variable
语句引发错误ReferenceError: Cannot access 'variable' before initialization
。 TDZ 仅存在于此内部作用域内。
6. 结论
TDZ 是一个重要概念,会影响 const
,let
和 class
语句的可用性。不允许在声明前使用变量。
当你可以在声明之前使用 var
变量时,它们会继承旧的行为。你应该避免这样做。
在我看来,当把良好的编码实践进入语言规范时,TDZ 就是其中的一个好东西。
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你提高开发效率的现代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把HTML转成PDF的4个方案及实现
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。