var、let、const

289 阅读6分钟

ES6中引入了两种变量声明方式 letconst ,下面比较 varletconst 之间的区别

var 声明及变量提升

在全局作用域或函数作用域中使用 var 声明的变量,不管在哪里声明,都会被当做在作用域顶部声明的变量,这就是 var 变量提升机制。

下面以函数为例来说明:

function fun(arg) {
    console.log(value,1);
    if(arg){
        var value = 10;
        console.log(value,2);
    }else{
        console.log(value,3);
    }
    console.log(value,4)
}
fun(true);
/*
	undefined 1
	10 2
	10 4
*/
fun();	
/*
	undefined 1
	undefined 3
	undefined 4
*/

在预编译阶段,JS引擎讲fun函数修改成下面这样:

function fun(arg){
    var value;
    //相同代码
}

在函数执行过程中,不管 if 条件是否成立,变量 value 都会在函数顶部被创建,此时变量尚未初始化,所以其值为undefined。if 条件成立,则会为 value 赋值为 10 ,否则 value 值一直为 undefined 。
在上面例子中,了解 var 声明变量提升机制,还有一种声明式函数提升。

var fun = 10;
function fun(){
    console.log("1111111");
}
fun();   //会报错,提示:fun is not a function ,fun不是一个函数。

这里应该可以执行函数才对啊,为什么会报错呢?我们来看经过预编译之后代码修改过后的样子:

function fun(){
    console.log("1111111");
}
var fun = 10;
fun();

相信你们也看得出了,声明式函数提升在 var 变量提升之上,由于 fun 函数与变量 fun 同名了,所以函数就被覆盖了,这里 fun 就赋值为 10 了,无法被执行。

let、const 块级声明

块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域(也称词法作用域)存在于:

  • 函数内部
  • 字符{ 和 }之间的区域

let 声明

let 用法与 var 的一致,用 let 代替 var 来声明变量,可以把变量的作用域限制在代码块中。但 let 声明并不像 var 那样被提升,因此使用 let 声明语句都应放在封闭代码块的顶部,这样整个代码块都能访问。

function fun(arg){
    if(arg){
        let a = 10;
        // 这里可以访问变量 a
    }else{
        // 这里不可以访问变量 a
    }
    // 这里不可以访问变量 a
}

由于变量 a 被 if 代码块所包含,所以在 if 之外都没有办法访问到。

const 声明

使用 const 声明的一般是常量,值一旦被绑定之后就不能重新绑定,而且每个使用 const 声明的变量必须要进行初始化。示例:

const a = 1;
a = 2;  //报错,不能重新赋值

const number;   //报错,没有进行初始化

如果常量是对象的话,则不允许修改绑定的值,但允许修改该对象的属性值。示例:

const object = {
    hello: "ES6"
}
object.hello = "JavaScript";    //不会报错,修改成功

const 与 let 声明都是块级标识符,所以常量也是当前代码块内有效,一旦执行完代码块就会立即被销毁,同样const声明也不会提升。

禁止重复声明

在同一个作用域之下,只有 var 声明可以重新声明同名的变量,let和const不可以,否则会报错。示例:

var a = 10;
var b = 20;
var c = 30;

var a = 40;     // 可以执行,并且 a 赋值为40
let b = 50;     // 报错
const c = 60;   // 报错

可别忘了,var 与 let\const 的作用域不同。

var value = 10;
if(true){
    let value = 20; // 不会报错
}

临时死区

临时死区 简称为 TDZ
let 和 const 声明变量不会提升到作用域的顶部,如果在声明前访问这些变量,会报错。

//示例1:
console.log(number);    //报错
let number = 100;

//示例2:
var a = 10;
if(true){
    console.log(a); //报错
    let a = 20;
}

由于在JS引擎在扫描代码发现变量声明时,对于是 var 声明的就会把它提升到函数作用域或全局函数作用域的顶部;对于是 let\const 声明的,就会把它放到 TDZ 中。在代码运行中,访问到 TDZ 中的变量就会报错,只有在执行过 let\const 语句后,才会把该语句声明的变量从 TDZ 中移除。

示例2的 if 代码块中,访问变量 a 是 TDZ 中的,而不是 if 之外的变量a,访问它必然报错。

var、let、const循环中块级作用域绑定

for 循环

ES6之前,var 声明让开发者在循环中创建函数变得十分困难,因为变量在循环之后依然可以访问。
示例:

for (var i = 0; i < 3; i++) {
    setTimeout(function (){
        console.log(i);
    },10);
}
// 控制台打印3次 3;

这里看似应该会打印1、2、3,但并没有。由于3个定时器都是访问 i 都是同一个作用域下的 i ,循环执行完后 i 的值为 3,所以打印了3次 3。
修改后:

for (var i = 0; i < 3; i++) {
    (function (i){
        return setTimeout(function (){
            console.log(i);
        },10);
    })(i);
}
// 控制台打印 1,2,3

这里使用闭包来保存定时器中的 i 值。虽然闭包使得函数变量得以保存,但会消耗内存,使用不当还会造成网页性能问题。
使用 let 声明来简化循环的过程,就像这样:

for (let i = 0; i < 3; i++) {
    setTimeout(function (){
        console.log(i);
    },10);
}
// 控制台打印 1,2,3

每次迭代循环都会创建一个新变量,并以之前迭代同名变量的值来初始化,每次循环内部创建的定时器都能得到属于它们自己 i 的副本。
这样听上去,好像 const 在for中也同样可以做到,然而使用 const 声明 i 的话,只有迭代执行一次,后续就无法执行了。因为执行一次后要执行 i++ ,由于 i 是常量,所以报错。

for-in、for-of 循环

在for-in、for-of循环中,let、const 表现就完成一样了,两者唯一不同的是 const 无法中循环中改变值。之所以能在for-in和for-of内运行,是因为每次迭代不会修改已绑定的值,而是 重新创建一个新的绑定

var object = {
    a: 1,
    b: 2
}
for(let key in object){
    key = key + '1';    // 可修改key
    setTimeout(function (){
        console.log(key);
    },10)
}
// 控制台打印 a1 , b1

for(const key in object){
    // 不能修改key
    setTimeout(function (){
        console.log(key);
    },10)
}
// 控制台打印 a , b

全局块作用域绑定

var 和 let、const另外一个区别是它们在 全局作用域下 的表现。当 var 语句用于全局作用域下,它会创建一个新的全局变量作为全局对象 ( 浏览器环境中的window对象 ) 的属性,所以用 var 可能会覆盖已存在的全局属性。

//示例1:
window.a = 1;
var a = 10;
console.log(window.a);  //打印 10
console.log(a);         //打印 10

//示例2:
window.key1 = 1;
let key1 = "a";
console.log(window.key1);   //打印 1
console.log(key1);          //打印 a

示例1中,从两次打印结果看,var 语句覆盖了 window对象同名的属性;示例2中,let 声明的变量并没有覆盖window下同名的属性,只是遮盖该属性。开发中,如果不想为全局对象创造属性,则使用 let 和 const 要安全得多。

总结

声明方式 变量提升 重复声明 临时死区 重新赋值 创建全局属性
var YES YES NO YES YES
let NO NO YES YES NO
const NO NO YES NO NO