1、var声明及变量提升(Hoisting)机制
在函数作用域或全局作用域中通过关键字var声明的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量,这就是我们常说的提升(Hoisting)机制。例如:
function getValue(condition){
if(condition){
var value = "blue";
return value;
}else{
//此处可访问变量value,其值为undefined
return null;
}
//此处可访问变量value,其值为undefined
}
在预编译阶段,JavaScript引擎会将上面的getValue函数修改成下面这样:
function getValue(condition){
var value; //变量声明被提升到函数顶部
if(condition){
value = "blue";
return value;
}else{
return null;
}
}
变量value的声明被提升至函数顶部,而初始化操作依旧留在原处执行,这就意味着在else子句中也可以访问到该变量,且由于此时变量尚未初始化,所以其值为undefined。
2、块级声明let
块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域存在于:
- 函数内部
- 块中(字符 “{” 和 “}” 之间的区域)
let 声明的用法和var相同。用let代替var来声明变量,就可以把变量的作用域限制在当前代码块中。由于let声明不会被提升,因此开发者通常将let声明语句放在封闭代码块的顶部,以便整个代码块都可以访问。
function getValue(condition){
if(condition){
let value = "blue";
return value;
}else{
//变量value在此处不存在
return null;
}
//变量value在此处不存在
}
3、禁止重声明
var count = 30;
let count = 40;//抛出语法错误
4、const声明
(1)const声明的变量必须进行初始化,且一旦设定后不可更改。
(2)const与let声明的都是块级标识符,所以常量也只在当前代码块内有效,一旦执行到块外会立即被销毁。常量同样也不会被提升至作用域顶部。
if(condition){
const maxItems = 5;
}
//此处无法访问maxItems
在这段代码中,在if语句中声明了常量maxItems,语句执行一结束,maxItems即刻被销毁,在代码块外访问不到这个常量。
(3)const声明对象,不允许修改绑定,但允许修改属性值。
const person={
name:"yxy"
}
person.name="yff";//可以修改属性值
person={
name:"Greg"//抛出语法错误
}
5、临时死区(Temporal Dead Zone)
与var不同,let和const声明的变量不会被提升到作用域顶部,如果在声明之前访问这些变量,即使是相对安全的typeof操作符,也会触发引用错误。
(1)触发引用错误
if(condition){
console.log(typeof value);//引用错误
let value = "blue";
}
- 由于console.log(typeof value)语句会抛出错误,因此用let定义并初始化变量value的语句不会执行。
- 此时的value位于JavaScript社区所谓的“临时死区”中。
- JavaScript引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部,要么将声明放入TDZ中,访问TDZ中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从TDZ中移出,然后方可正常访问。
(2)不会触发报错的场景
console.log(typeof value);//引用错误
if(condition){
let value = "blue";
}
在let声明的作用域外对该变量使用typeof则不会报错。因为此时value并不在TDZ中,这也就意味着不存在value这个绑定,typeof操作最终返回“undefined"。
6、循环中的块作用域绑定
把声明的计数器变量限制在循环内部。
for(var i=0;i<10;i++){
process(items[i]);
}
console.log("i:"+i);//i在此处仍可以访问
在JavaScript中,由于var声明得到了提升,变量i在循环结束后仍可访问。如果换用let声明变量就能得到想要的结果。
for(let i=0;i<10;i++){
process(items[i]);
}
console.log("i:"+i);//i在此处,不可以访问。会抛出一个错误
7、全局块作用域绑定
当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境中的window对象)的属性。即用var很可能会无意中覆盖一个已经存在的全局属性。
var RegExp = "hello";
console.log(window.RegExp);//"hello"
console.log(window.RegExp === RegExp);//true
var yff = "hi";
console.log(window.yff);//"hi"
console.log(window.yff === yff);//true
但如果你在全局作用域使用let或const,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。即,用let或const不能覆盖全局变量,只能遮蔽它。
let RegExp = "hello";
console.log(window.RegExp);//"hello"
console.log(window.RegExp === RegExp);//false
const yff = "hi";
console.log(window.yff);//"hi"
console.log(window.yff === yff);//false
总结:var声明的变量会创建全局对象属性(window),而let和const不会。
如果希望在全局对象下定义变量,仍可以使用var,这种情况常见于在浏览器中跨frame或跨window访问代码。
8、总结
当前使用块级绑定的最佳实践是:默认使用const,只在确实需要改变变量时使用let。这样就可以在某种程度上实现代码的不可变,从而防止某些错误的产生。