likes
comments
collection
share

ES6的块级作用域和暂时性死区是什么?

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

「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战

在上篇文章中我们了解了函数作用域和全局作用域,今天我们将介绍块级作用域和暂时性死区

作用域

简单来说,作用域就是用来规定变量的作用范围的,在任何语言当中都会有作用域的概念,在ES6以前,JavaScript只有函数作用域和全局作用域,ES6之后又增加块级作用域。

函数作用域和全局作用域

什么是JavaScript作用域、作用域链?

块级作用域和暂时性死区

在ES6中带来了const和let变量声明的新关键字和块级作用域,在其他语言中普遍都存在的概念,在这之前JavaScript并没有块级作用域的概念,随着块级作用域的还有“暂时性死区”的概念,说到这个,我们要先从JavaScript的“变量提升”来说起

请看下面的代码

function foo() {
  console.log(bar)
  var bar = 3
}
foo()

如果你对JavaScript有一定的了解,就会知道上面的代码输出undefined,因为bar变量在函数作用域内进行了提升。

上面的代码和下面的代码是等价的

function foo() {
  var bar
  console.log(bar)
  bar = 3
}
foo()

但当使用let对bar变量进行声明时,则会报错Uncaught ReferenceError: Cannot access 'bar' before initialization,请看下面代码:

function foo() {
  console.log(bar)
  let bar = 3
}
foo()

我们知道,使用let或者const声明变量时会针对这个变量形成一个封闭的块级作用域,在这个块级作用域中,如果在声明变量前访问这个变量,就会报ReferenceError错误;如果在声明变量后访问,则可以正常获取变量值,如下:

function foo() {
  let bar = 3
  console.log(bar)
}
foo()

上面的代码将正常输出3。所以,在相应花括号形成的作用域中存在一个“死区”,开始于函数的开头,结束于相关变量声明语句的所在行。在这个范围内无法访问使用let或者const声明的变量。这个“死区”的专业名称为TDZ(Temporal Dead Zone)。

文字描述不好理解,看下图更容易理解:

ES6的块级作用域和暂时性死区是什么?

在foo函数中,let bar = 3这一行的前面的区域称为“死区”,在“死区”内访问变量bar会报错,而在“死区”外可正常访问。

上图中圈出的暂时性死区,也有一种比较极端的场景,函数的参数默认值也会收到它的影响,比如下面的代码:

在下面的foo函数中,如果没有传入第一个参数,则会使用第二个参数作为第一个实参。

function foo(arg1 = arg2, arg2) {
  console.log(arg1, arg2)
}
foo('arg1', 'arg2')

运行上面代码,返回正常。

但当第一个参数为默认值时,执行arg1 = arg2会被当作暂时性死区处理,代码如下:

function foo(arg1 = arg2, arg2) {
  console.log(arg1, arg2)
}
foo(undefined, 'arg2')

// Uncaught ReferenceError: Cannot access 'arg2' before initialization

上面代码运行报错是因为除了块级作用域以外,函数参数默认值也会受到暂时性死区影响。

我们来再看一个例子,下面的代码将输出什么呢?

function foo(arg1 = arg2, arg2) {
  console.log(arg1, arg2)
}

foo(null, 'arg2')

上面代码执行将输出null, arg2。这里就涉及到undefined和null的区别了。在执行foo(null, 'arg2')时,函数并不会使用arg1的默认值,而是直接接受null作为参数arg1的值。

我们再来看另外一个场景

function foo(arg1){
  let arg1
}
foo('arg1')

// Uncaught SyntaxError: Identifier 'arg1' has already been declared

运行这段代码将会报上面的错误,因为函数参数名出现在其“执行上下文/作用域”导致的。

就像下面函数第一行已经声明了arg1变量,函数内再用let声明就会报错,这也是let声明变量的特点。

function foo(){
  var arg1
  let arg1
}
foo('arg1')

// Uncaught SyntaxError: Identifier 'arg1' has already been declared

通过debugger可以发现,函数的参数变量存在于的函数作用域内。

ES6的块级作用域和暂时性死区是什么?

在下篇文章中,我们将介绍JavaScript的执行上下文和调用栈的相关内容。

欢迎我的公众号【小帅的编程笔记】,让自己和他人都能有所收获!