块级作用域绑定(临时死区)(var && let && const 的区别和联系)

导语

过去, javascript 的变量声明机制一直令我们感到困惑。 大多数类c语言在声明变量的同时也会创建变量(绑定)。 而在以前的javascrpit 中, 何时创建变量要看怎么声明变量。 es6 的新语法可以帮助你更好地控制作用域. 本文将解释为什么经典的var 容易让人迷惑 。

1 .var 声明&提升机制(Hoisting)

在函数作用域或全局作用域中通过关键字var 声明的变量 , 无论实际上是在哪里声明的 , 都会被当成在当前作用域顶部声明的变量 , 这就是我们常说的提升机制
例如:
```function getValue () { if(0) { var value = 'blue'; return value ; } else { return null } } `

解释
: 如果你不熟悉javascript , 可能会认为只有当condition的值为true 时才会创建变量value 。 事实上, 无论如何变量 value 都会被创建。 在预编译阶段, js 引擎会将上面的getValue函数修改成下面这样:



`function getValue () {
    var value ; 
    if(0) {
        value = 'blue' ;
        return value ;
    } else {
        return null ;
    }
}

变量声明被提升至函数作用域顶部, 而初始化操作依旧留在原处执行, 这就意味着在else子句中也可以访问到该变量 , 而且由于此时变量尚未初始化 , 所以其值为undefined.


## 2. 块级声明

块级声明用于声明在指定块的作用域之外无法访问的变量 , 块级作用域存在于 :

  • 函数内部
  • 块中{}

很多类c语言都有块级作用域 , 而es6 引入就上为了让js 更灵活和普适

### let 声明
let 声明的用法与var 相同。 用let 代替var来声明变量, 就可以把变量的作用域限制在当前代码块中 , (稍后我们将在临时死区一节中讨论另外几处细微的语法差异), 由于let 声明不会被提升, 因此开发者将let 声明语句放在封闭代码块的顶部 , 以便整个代码块都可以访问, 例如 :

```
function getValue (condition) {
    if(condition) {
        let value = 'blue' ;
        return value ;
    } else {
        //变量value 不存在
        return null
    }
    //变量value 在此处不存在
}

现在这个getValue 函数的运行结果更像类c语言. 变量value改由关键字let 进行声明后 , 不再被提升至函数顶部。 执行流离开if 块value立刻被销毁。 如果condition 的值false ,就永远不会声明并初始化value.

禁止重声明

假设作用域中已经存在某个标志符, 此时再使用let 关键字声明它就会报错 , 比如:

var  count = 30 ;
//抛出错误
let count = 10 ;

解释:同一作用域中, 不能用let 重复定义已经存在的标志符, 所以此处的let 声明会抛出错误。但如果当前作用域内嵌另一个作用域, 便可在内嵌的作用域中用let 声明同名变量,例如 :


var count = 30 ; 
if(1) {
    //不会抛出错误
    let count = 10 ;
}

由于此处的let是在if块内声明了新变量count, 因此不会抛出错误。 内部块中的count会遮蔽全局作用域中的count , 后者只有在if块外才能访问到

const 声明

es6标准还提供了const关键字。使用const 声明的变量是常量,其值一旦被设定后不可更改。因此,每个通过const声明的常量必须进行初始化,例如

// 有效的常量
const maxItem = 30 ;

//语法错误 , 常量未初始化
const name ;

const与let

相同点

const 与let 声明的都是块级标识符,

  1. 所以常量也只在当前代码块内有效,一旦执行到块外会立即被销毁
  2. 在同一作用域用const 声明已经存在的标识符也会导致语法错误,无论该标志符是使用var 还是let声明的,比如

    var message = 'hello' ;
    let age = 25 ; 
    
    //这俩条都会抛出错误
    const message = 'goodbye' ;
    const age = 30

后俩条const 声明语句本身没问题, 但由于前面用var 和let 声明了俩个同名变量,结果代码就无法执行了

尽管相似之处很多, 但有一个很大的不同,即无论是在严格模式下还是非严格模式下,都不可以为const定义的常量再赋值, 否则会抛出错误


const maxItems = 5 ;
//抛出错误
maxItem = 6 ;

然而js与其他语言不同的是, javascript中的变量如果是对象,则对象中的值可以修改

用const 声明对象

const声明不允许修改绑定, 但允许修改值,比如


const person = {
    name: 'lihua'
}
//可以修改对象属性的值
person.name = 'hanmeimei'

//抛出错误
person = {
    name:'hanmeimei'
}

切记:如果直接给person赋值, 即要改变person的值, 就会抛出错误。const声明不允许修改绑定,但允许修改值

临时死区(Temporal Dead Zone)

与var 不同,let 和 const 声明的变量不会被提升到作用域顶部, 如果在声明之前访问这些变量,
即时是相对安全的typeof操作符也会触发引用错误,例如:


if(1) {
    console.log(typeof value) ;//引用错误
    let value = 'blue'
}

由于console.log(typeof value)语句会抛出错误, 因此用let定义并初始化变量value的语句不会执行。此时的value还位于js所谓的临时死区(TDZ)中。虽然es标准并没有明确提到TDZ,但人们常用来它来描述let和const的不提升效果。

解释:javaScript引擎在扫描代码发现变量声明中,要么将他们提升至作用域顶部(遇到var 声明),要么将声明放到TDZ中(遇到let 和const声明)。访问TDZ中的变量会触发运行时错误。只有执行过变量声明的语句后,变量才会从TDZ中移出,然后方可正常访问。

在声明前访问由let定义的变量就是这样。由前面实例可见,即便是相对不容易出错的typeof操作符也无法阻挡引擎抛出的错误。但在let声明的作用域外对该变量使用typeof则不会报错,具体如下:
    
    console.log(typeof value) ;//undefined
    
    if(1) {
        let value = 'blue'
    }

typeof 是在声明value的代码块外执行的,此时value并不在TDZ中。这也就意味着不存在value这个绑定,typeof操作最终返回undefined

小结

块级作用域绑定的let和const为javaScript引入了词法作用域,他们声明的变量不会提升,而且只可以在声明这些变量的代码块中使用。如此一来,javascript声明变量的语法与其他语言就更像了。同时也降低了产生错误的可能。与此同时,在声明前访问块级变量会导致错误,因为变量还在临时死区(TDZ)中。

关于作者

本文部分借鉴了其他大佬的作品(与其说借鉴,不如说是直接抄,哈哈),只是为了分享和复习,侵权立删

你可能感兴趣的:(chrome,node.js,javascript)