javaScript中的暂性死区

Don't Use JavaScript Variables Without Knowing Temporal Dead Zone

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

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

new Car('red'); // Does it work?

class Car {
  constructor(color) {
    this.color = color;
  }
}

第二个,先引用后定义一个函数:

greet('World'); // Does it work?


function greet(who) {
  return `Hello, ${who}!`;
}

正确答案是:第一段用class定义的代码,会产生一个 ReferenceError 错误,第二个运行正常。

如果你答错了或者没明白发生了什么,那么你需要掌握暂性死区(TDZ)。TDZ作用于 letconstclass语句。他对于js的变量的工作方式非常重要。

1.什么是暂性死区

我们从一个简单的const声明开始。先声明并初始化变量并且访问他,完全ok。


const white = '#FFFFFF';

white; // => '#FFFFFF'

下面我们尝试定义前访问变量white:

white; // throws `ReferenceError`

const white = '#FFFFFF';

white;

直到 const white = '#FFFFFF'语句,变量white一直在TDZ中。
如果访问了TDZ中的white, JavaScript会抛错,ReferenceError: Cannot access 'white' before initialization.

MacHi 2019-10-09 19-39-33.png

TDZ语义强制要求访问前必须先定义,强制要求:声名必须先于使用。

2. 让我们看看受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声明

如前面所说,直到class定义前不能正常使用:


// Does not work!
const myNissan = new Car('red'); // throws `ReferenceError`

class Car {
  constructor(color) {
    this.color = color;
  }
}

想要正常使用,必须保证先使用class定义:


class Car {
  constructor(color) {
    this.color = color;
  }
}

// Works!
const myNissan = new Car('red');
myNissan.color; // => 'red'

2.4 constructor() 中的super()

如果继承了父类,在调用构造器中的super()前
If you extend a parent class, before calling super() inside the constructor, 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()中, 直到super()调用后 this才能正常使用。

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 函数默认参数

默认参数作用于当前作用域中,与全局作用域是分开的。默认参数同样受制于TDZ:


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

a声明前,参数a会被表达式a = a右边的a所使用, 这样会产生一个a的引用错误。
保证参数在使用前声明和初始化,我们用一个特殊的变量init来初始化。


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

3. var, function, import 语法

与上述相反,varfunction不会收到TDZ影响,他会被所在的作用域提升。
如果在声明前访问var定义的变量,通常会得到undefined


// Works, but don't do this!
value; // => undefined

var value;

但是,function可以正常使用无论他在哪里声明。


// Works!
greet('World'); // => 'Hello, World!'

function greet(who) {
  return `Hello, ${who}!`;
}

// Works!
greet('Earth'); // => 'Hello, Earth!'

通常我们不关心函数的实现只是想调用它。所以函数在定义前可以被执行是说的通的。

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


// Works!
myFunction();

import { myFunction } from './myModule';

import会被提升,所以最佳实践是在文件头部import依赖模块。

4. typeof 在TDZ中的行为

typeof运算符可以用来确认变量是否在当前作用域被定义。

比如,notDefined变量没有定义,在这个变量上使用typeof不会抛错:


typeof notDefined; // => 'undefined'

由于为定义的关系,所以typeof notDefined的运算结果是undefined
但是在位于TDZ中的变量使用typeof操作符会产生不同的行为。下面的例子会抛错:


typeof variable; // throws `ReferenceError`

let variable;

这个引用错误背后的原因是你可以静态的(仅通过查看代码)在能确认variable被定义。

5. TDZ在当前作用域中的行为

TDZ所在作用域内的相关声明都会收其影响。


2.png

例子:


function doSomething(someVal) {
  // Function scope
  typeof variable; // => undefined
  if (someVal) {
    // Inner block scope
    typeof variable; // throws `ReferenceError`
    let variable;
  }
}
doSomething(true);

这里有2个作用域:

  1. function 作用域。
  2. let定义的内部块级作用域。

在函数作用域,typeof variable简单求值运算得到undefined。 这里let variable不受TDZ影响。

内部作用域中的typeof variable语句,因为在声明前使用变量,会抛出一个错误ReferenceError: Cannot access 'variable' before initialization。TDZ仅影响内部所在的作用域。

6. 总结

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

相反的可以在声明前使用var定义的变量,这是一个旧的机制。我们应该避免它。

在我看来,好的编码习惯来达到语言规范TDZ是一个好东西。

原文地址


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351