美文网首页web前端技术分享
javaScript中的暂性死区

javaScript中的暂性死区

作者: 土肥圆_c1ab | 来源:发表于2019-10-09 21:02 被阅读0次

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是一个好东西。

原文地址


相关文章

  • javaScript中的暂性死区

    Don't Use JavaScript Variables Without Knowing Temporal D...

  • Javascript 面试经典(一)

    js暂时性死区 在ES6中新增的定义变量的关键字let和const,会带来一个暂时性死区的问题,暂时性死区简单来讲...

  • Javascript面试常问点

    js暂时性死区 在ES6中新增的定义变量的关键字let和const,会带来一个暂时性死区的问题,暂时性死区简单来讲...

  • let和const学习

    let和Const命令 let命令 暂时性死区 使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。暂...

  • let、const和var的区别

    var的问题: let的问题: const的问题: 暂时性死区: 暂时性死区的本质就是,只要一进入当前作用域,所要...

  • 暂时性死区

    函数的执行上下文是一门语言最重要/最容易引起误解的知识点. JavaScript的执行上下文也是被吐槽最多的点. ...

  • ES6 let const

    暂时性死区:只要块级作用域内存在let命令,它所声明的变量就绑定这个区域,不再受外部影响。 暂时性死区:let.c...

  • JS高级知识点

    TDZ(暂时性死区) 学习暂时性死区之前需要先了解一下var,let和const的区别(变量提升可分为创建提升和初...

  • ES6的let命令(二)

    2.暂时性死区 暂时性死区是指只要块级作用域内存在let命令,它所声明的变量就绑定这个作用域,不会受到外部的影响。...

  • TDZ暂时性死区

    由阮一峰老师的一条微博引发的 TDZ 思考 var 和 let 声明的变量在发生声明提升时,初始化(initial...

网友评论

    本文标题:javaScript中的暂性死区

    本文链接:https://www.haomeiwen.com/subject/vrxnpctx.html