likes
comments
collection
share

不知道临时死区就不要使用JavaScript变量

作者站长头像
站长
· 阅读数 55

作者:Dmitri Pavlutin 原文:dmitripavlutin.com/javascript-…

先问个简单的问题,下面哪段代码会报错? 第一段代码是先创建实例然后再定义用到的类:

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

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

或者是第二段先调用再定义函数?

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

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

正确答案:第一段代码,在new对象时报ReferenceError。第二段成功执行。

如果你的答案和上面的不一样,或者你不知道底层的原理直接猜了个答案,就说明你需要了解临时死区(Temporal Dead Zone以下简写为TDZ)。

TDZ管理let, constclass语句的可用性,所以了解JavaScript中变量是如何工作的很重要。

1.什么是临时死区

先从简单的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

不知道临时死区就不要使用JavaScript变量

“临时死区的语义中禁止在变量声明前访问,它强调了规则:在声明前不能使用任何东西”

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语句

就像在介绍中看到的,不能在定义前使用class:

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

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

要使得上面代码正确,在class定义之后再使用:

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

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

2.4 constructor()内的super()

如果要扩展父类,在constructor内调用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()内,thissuper()调用前是不能使用的。 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默认函数参数

默认参数在中间作用域里,与全局作用域和函数作用域分离。默认参数同样受到DTZ的限制:

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和函数定义不受TDZ的影响,他们会被提升到当前作用域中。 如果在声明前访问var变量,必然得到undefined

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

var value;

然而,不管函数在哪里定义,都可以使用它。

// 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。 但在TDZ中typeof操作符用在变量上就会有不同的结果,下面的例子,JavaScript会报错:

typeof variable; // throws `ReferenceError`

let variable;

引用错误背后的原因是你可以静态(直接看代码)的确定变量已经定义过了。

5. TDZ只在当前作用域中有效

临时死区只在声明语句当前所在的作用域下影响变量。

不知道临时死区就不要使用JavaScript变量

看个例子:

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

代码中有两个作用域: 1.函数作用域 2.let变量定义所在的内部块作用域 在函数作用域中,typeof variable就等于undefined。在这里let变量的TDZ没用效果。 typeof variable的内部作用域中,在声明前使用变量就抛出了ReferenceError:Cannot access 'variable' before initialization。TDZ只存在内部作用域中。

6. 总结

TDZ是个很重要的概念,它影响了const, letclass语句的能力,它使得变量在声明前不允许使用。 相反,你可以在声明前使用var变量,它继承了老的传统,应该尽量避免使用。 我的观点是,实际情况下当好的编码涉及到语言规范时,TDZ就是一种好的选择。

转载自:https://juejin.cn/post/6844903958356459527
评论
请登录