var声明及变量提升(Hoisting)机制
先看例子
function getValue (condition){
if(condition){
var value = 'blue';
// 其他代码
return value
} else {
return value
}
}
在函数或全局作用域中,使用 var 来声明的变量,无论在代码的哪里进行声明,都会被当作在当前作用域顶部声明的变量,这就是我们常说的提升(Hoisting)机制。
例:
function getValue (condition){
var value
if(condition){
value = 'blue';
// 其他代码
return value
} else {
// 此处访问变量value值为 undefined
return value
}
// 此处访问变量value值为 undefined
}
变量value的声明被提升到了顶部,这就意味着else语句中也可以访问到该变量并且由于还未进行初始化,所以访问到的值为 undefined,变量提升需要一些时间来进行习惯和熟悉,避免因为误解而产生bug。
块级声明
ES6引入的块级作用域来强化对变量生命周期的控制,块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域存在于:
- 函数内部
- 代码块中(字符{和}之间区域)
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块会立即被销毁,如果condition为false就永远不会声明并初始化value。
禁止重声明
例:
var count = 30;
let count = 40;
这段代码由于count被声明了两次,第一次用的是var 第二次用的是 let。如果作用域里已经存在某个标识符,此时在进行let声明,就会抛出错误信息。
例:
var count = 30;
if (condition) {
let count = 40;
}
因为此处的let是在if的代码块内声明了新变量count,因此不会抛出错误。内部代码块里的count会遮蔽全局作用域中的count,后者只能在if代码块之外才能访问到。
const 声明
ECMAScript 6 标准还提供了 const 关键字。 使用const关键字声明的是常量,其值一旦被设定后不可更改,因此,每个通过 const 声明的常量必须进行初始化。
例
const maxItems = 30;
const name;
这里在声明 maxItems 时进行了初始化操作, 而声明 name 时没有赋值,因此执行后者会抛出语法错误。
const 与 let
const 与 let 都是块级声明,所以常量也只是在当前的代码块有效,一旦执行到代码块外会立即被销毁,常量同样也不会被提升到作用域顶部。
例
if(condition){
const maxItems = 5;
// 其他代码
}
// 此处无法访问 maxItems
在这段代码中,在 if 语句中声明常量 maxItems ,语句执一结束,maxItems 即刻被销毁,在代码块外访问不到这个常量。
与 let 相似,在同一个作用域用 const 声明已经存在的标识符也会导致语法错误,无论该标识符是使用的 var(在全局或函数作用域中),还是 let(在块级作用域中)声明的。
var message = 'hello!';
let age = 25;
// 这两条语句都会抛出错误
const message = 'goodbye';
const age = 30;
后两条 const 声明语句本身没问题,但由于前面用 var 和 let 声明了两个同名变量,结果代码就无法执行了。
尽管相似之处很多,但 const 声明与let声明有一处很大的不同,即无论在严格模式还是非严格模式下,都不可以为 const 定义的常量再进行赋值,否则会抛出错误。
const maxItems = 5;
// 抛出语法错误
maxItems = 30;
ECMAScript6 中的常量与其他语言中的很像,此处定义的maxItems不可以再被赋值。但是对象有所不同,对象中的值可以进行修改。
用 const 声明对象
const person = {
name: 'Sherry'
};
// 可以修改对象属性的值
person.name = 'Nocte'
// 抛出语法错误
person = {
name:'Nocte'
}
在这段代码里,绑定 person 的值是一个包含了一个属性的对象,改变person.name 的值,不会抛出任何错误,因为修改的是 person 包含的值。如果直接给 person 赋值,即需要改变 person 的绑定,就会抛出错误。切记,const 声明不允许修改绑定,但允许修改绑定的值。
临时死区(Temporal Dead Zone 后续简称为 TDZ)
与 var 不同,let 和 const 声明的变量不会被提升到作用域顶部,如果在声明之前访问这些变量,即使是使用相对安全的 typeof 操作符也会触发饮用错误。
例
if(condition){
console.log(typeof value); // 饮用错误
let value = 'Sherry'
}
由于 console.log(typeof value) 语句会抛出错误,因此用 let 定义并初始化变量 value 的语句不会执行。此时的 value 还位于 JavaScript 社区所谓的"临时死区"(Temporal Dead Zone)或TDZ中。虽然ECMAScript标准并没有明确提到TDZ,但人们却常用它来描述 let 和 const 的不提升效果。TDZ导致的声明位置的微妙差异对于 let 和const 是一样的。
JavaScript 引擎在扫描代码发现变量声明时,要么将他们提升至作用域顶部(var声明),要么将生命放到TDZ中(let和const声明)。访问TDZ中的变量或常量会触发运行时的错误。只有执行过声明语句后,才会从TDZ中移除,然后可以正常访问。
由上一个例子可见,即便是不易出错的 typeof 操作符也无法阻挡引擎抛出错误。但在 let 声明的作用域外对该变量使用typeof则不会报错。
例:
console.log(typeof value);
if(condition){
let value = 'Sherry'
}
typeof 是声明在变量 value 的代码块外执行的,此时 value 并不存在TDZ中。这也就意味着不存在 value 这个绑定,typeof最终返回"undefined"。TDZ只块级绑定的特性之一
钟楠@好乐互娱