var、let、const变量声明的区别及特点

121 阅读4分钟

一、var变量提升机制及特点

在函数作用域或全局作用域中通过var声明的变量,都会被当成在当前作用域顶部声明的变量,这就是变量提升机制,下面用一个函数例子说明:

function getAge(name){
    if(name){
        var age = '28';
        return age;
    }
    // 这里也能访问到if语句里的age变量,不过因为变量未初始化,结果为undefined
    console.log(age);   
}

其实js在预编译阶段,会把代码修改成下面这样子:

function getAge(name){
    var age;
    if(name){
        age = '28';
        return age;
    }
    console.log(age);  // 结果也为undefined
}

从上面的示例中可以看到,变量age的声明被提升至函数顶部,而初始化操作依旧在if语句中完成。 不过还有一点要注意的是,除了var有提升机制,函数也有,请看下面示例:

console.log(age) // ƒ age() {console.log(123);}
age(); // 28
var age = '28'
function age(){
    console.log('28');
}
age() // Uncaught TypeError: age is not a function

上述示例说明:函数提升与变量提升同时存在时,函数提升优先级高于变量提升并且,并且不会被变量声明覆盖,但是当变量赋值之后age函数被覆盖。

注:还有一个要注意的点,当var被用于全局作用域时,它会创建一个全局对象(浏览器环境中的window对象)的属性,所以使用var在全局作用域声明的时候要特别注意,全局对象中是否存在这属性,防止被覆盖

var age = '40';
console.log(age);  // 40
console.log(window.age) // 40

var RegExp = "hello,world";
// 本来window全局对象中有js内置的RegExp函数,结果被var声明的RegExp变量给覆盖了。
console.log(window.RegExp);  // hello,world

二、块级作用域绑定

1、let声明

在上面我们提到var声明会被提升至作用域顶部,由此可能会引发一些奇怪的BUG,正因为如此, ES6 引入了块级声明(let、const),块级声明也就是让所声明的变量在指定块级作用域外无法被访问,让变量的生命周期更加可控。还是以上面的例子说明:

function getAge(name){
    if(name){
        let age = '28';
        return age;
    }
    // 如果使用let块级声明,这里会报错:
    // Uncaught ReferenceError: age is not defined
    console.log(age);   
}

上述例子说明age变量只有在if代码块里可以访问,其它地方无法访问该变量,这说明let会在一个函数内部或在一个代码块(由一对花括号包裹)内部会创建一个块级作用域。 下面这个例子也很好的说明了这个问题:

   for(var i = 0;i<10;i++){}
   console.log(i); // 10

因为var声明的变量被提升到作用域顶部,循环体外也可以访问,也导致了变量i在循环的每次迭代中都被共享了,所以得到的结果是:10, 但使用let就可以解决这个问题:

   for(let i = 0;i<10;i++){}
   console.log(i); // Uncaught ReferenceError: i is not defined

使用let声明的话,i变量只在循环体内可以访问,其它地方都访问不到。 同时let也禁止重复声明:

var age="40";
var age = "50";
// 如果var声明,后面的声明会覆盖前面的声明,所以这里打印结果结果是:50
console.log(age); 

let name = "张三";
let name = "李四";
// 使用let重复声明,会出现报错:
// Uncaught SyntaxError: Identifier 'name' has already been declared
console.log(name);

let和var声明还有一点不同的是,在全局作用域中let声明不会像var一样会在全局window对象中创建一个属性。

var age = '40';
console.log(window.age === age); // true
let name = '张三';
console.log(window.name === name); // false

且let并不会像var声明被提升到作用域顶部

console.log(name);  // undefined
var name = '张三';
console.log(age); // 报错:Uncaught TypeError: age is not a function。
let age = '40';

因为var声明会被提升至作用域顶部,所以此时访问name是存在的,但是name此时并未被赋值,所以得到undefined,但是如果使用let或const来声明变量,因为它们并不具备提升机制,所以在声明前访问会报错,也称暂时性死区。

2、const声明

const的作用很多时候和let是一样的,但通过const声明的变量为基础数据类型时,它是不可变的,当它声明的变量为引用类型时,可以修改它的属性,如果要确保引用类型值的属性值不可变,请使用Object.freeze()冻结

   let age = '40';
   age = '50';
   console.log(age); // 50
   const name = '张三';
   name = '李四'; 
   console.log(name); // 报错:Uncaught TypeError: Assignment to constant variable.
   const person = {
       name:'张三'
   }
   person.name = '李四';
   console.log(person.name); // 李四

三、块级绑定的最佳实践

在ES6中一般情况下需要使用变量声明的地方都应该使用let或const替代var,在默认情况下建议使用 const,因为一般变量初始化之后都不应该被改变,有助于防止某些类型的错误,但变量值确实需要被更改的情况下才使用let。