共计 2856 个字符,预计需要花费 8 分钟才能阅读完成。
导语
过去, javascript 的变量声明机制一直令我们感到困惑。大多数类 c 语言在声明变量的同时也会创建变量(绑定)。而在以前的 javascrpit 中,何时创建变量要看怎么声明变量。es6 的新语法可以帮助你更好地控制作用域. 本文将解释为什么经典的 var 容易让人迷惑。
1 .var 声明 & 提升机制(Hoisting)
在函数作用域或全局作用域中通过关键字 var 声明的变量 , 无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量 , 这就是我们常说的提升机制 <br> | |
例如:<br> | |
```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 声明的都是块级标识符,
- 所以常量也只在当前代码块内有效,一旦执行到块外会立即被销毁
-
在同一作用域用 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) 中。
关于作者
本文部分借鉴了其他大佬的作品(与其说借鉴,不如说是直接抄, 哈哈), 只是为了分享和复习,侵权立删