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

2,757 阅读5分钟

var、let、const这些变量声明各有什么区别及特点,本文将从三个方面阐述他们差别:

  • 变量提升机制
  • 块级作用域绑定
  • 块级绑定的最佳实践

一、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。