详细总结JavaScript中的var、let、const的区别

105 阅读4分钟

        本文搬运的是我自己的博客,有兴趣可以移步至:总结JavaScript中的var、let、const的区别

         ES6以前JS只能使用var声明变量,ES6之后更新了letconst,我参照了**《JavaScript高级程序设计》第四版**对varletconst的区别做了一个简单的总结,在这里分享给大家。第一次写博客,如有错漏,欢迎大家批评指正。(๑•̀ㅁ•́ฅ)  

var

        关于var为什么会出现变量提升,在全局定义时会被挂在到window对象上,我在函数执行与作用域链这篇文章中有非常详尽的解释。如果你对底层原理感兴趣可以看一下。如果你还没有了解到什么是var的变量提升、作用域等问题,那么可以忽略我这段内容,自细阅读下面的内容即可。

1、松散类型

        ECMAScript是松散类型,变量可以用于保存任何数据类型

var a = 'string';
console.log(a);
a = 1;
console.log(a);
a = true;
console.log(a)

2、声明初始化undefined

        声明的变量初始化值是undefined

var a;
consloe.log(a)

3、声明作用域

        var作用域是函数作用域,也就是说它的作用范围是包含它的那个函数。

function say(){
    var a = 'hello';
    console.log(a);
}
console.log(a);

4、错误定义方式导致的全局污染

函数中声明一个变量却没有使用var时会变成全局变量,可以看到下面代码中的message在函数作用域外被访问到了

function test(){
    message = 'test';
}
test();
console.log(message);

        上面代码等同于下面代码:

var message;
function test(){
    message = 'test'
}
test();
console.log(message);

5、变量提升

        使用var时下面代码不是报错,因为var的变量声明会自动提升到函数作用域的顶部

function foo(){
    console.log(foo);
    var foo = 'foo';
}
foo();
// 输出undefined

        上面代码之所以输出undefined,是因为ECMAScript在运行时将它等价于了下面代码

function foo(){
    var foo;
    console.log(foo);
    foo = 'foo';
}
foo();

        所谓的变量提升就是把所有的变量声明都提升到函数的做顶部,除此之外,即使你声明很多个相同的变量也不会报错

function foo(){
    var age = 12;
    var age = 18;
    var age = 21;
    var age = '32';
    console.log(age);
}
foo();

let

        let跟var的作用差不多,但是有着非常重要的差别。

1、作用域

        块级作用域(var是函数作用域)

if(true){
    var name1 = 'var';
    console.log(name1); // var
}
console.log(name1); // var

if(true){
    let name2 = 18;
    console.log(name2); // 18
}
console.log(name2); // name is not defind

        之所以找不到name2是因为let声明的变量作用域是块级的,在这个块运行结束后会自动销毁。

2、冗余声明

        let不允许在同一作用域中冗余声明(就是重复声明),这样会导致报错

var name;
var name;

let age;
let age;//SyntaxError: 标识符age已经声明过了。

        冗余声明不会因为混用var和let而受影响,这两个关键字定义的并不是不同的变量,它们指出的是变量在相关作用域如何存在

var name;
let name;//SyntaxError

let age;
let name;//SyntaxError

        嵌套使用相同的标识符不会报错,因为它们属于不同的块

var name = 'var';
console.log(name); //var
if(true){
    var name = 'matt';//matt
    console.log(name);
}

let age = 18;
console.log(age); //18
if(true){
    let age = 89;
    console.log(age);//89
}

3、暂时性死区

        let与var的一个重要区别就是不存在变量提升

console.log(name)
var name = '苦哈哈';
console.log(age); //ReferenceError: age 没有定义
let age = 18;

暂时性死区: JavaScript在解析代码时也会注意到出现在块后面的let声明,但在let声明被执行前不能以任何形式来引用未声明的变量。这样的在let声明触发前的区域就被称之为占时性死区,在此阶段引用任何后面才声明的变量都会抛出ReferenceError。

4、全局声明

        与var不同的是,使用let声明的全局变量不会成为window对象的属性:

var name = '苦哈哈';
let age = 18;
console.log(window.name); // 苦哈哈
console.log(window.age);//undefined

5、循环变量中的let

        在let出现之前,for循环定义的迭代变量会渗透到外部:

for(var i = 0; i < 9 ; i++){
    // 循环逻辑
}
console.log(i);// 输出:9

        有了let之后这种问题就消失了,因为for的迭代变量的作用域仅限于循环体內部的块及作用域:

for(let i = 0; i < 9 ; i++){
    // 循环逻辑
}
console.log(i); //ReferenceError: i 没有定义

        在使用var进行for循环时最常见的问题就是迭代变量奇特的声明和修改:

//这个循环会输出5个5
for(var i = 0; i < 5; i++){
    setTimeout(()=>{
        console.log(i);
    },0);
}

        之所以会这样,是因为迭代变量保存的是导致退出循环的值,这使得在执行异步函数的时候所有的i都指向了那个导致循环退出的值。

        然而在使用let声明迭代变量时,每一次循环都会声明一个新的变量,每一个setTimeout引用的都是不同的变量实例,所以console.log()才能输出我们期望的值,也就是每次执行循环使得迭代变量值:

for(var i = 0; i < 5; i++){
    setTimeout(()=>{
        console.log(i);
    },0);
}
// 输出:0、1、2、3、4

const

        const与let基本相同,唯一重要的区别就是const声明的变量必须同时初始化,且尝试修改const声明的变量会导致运行时报错:

const age = 26;
age = 36; // TypeError: 给常量赋值

const的问题: const本质:const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

        const无法真正冻结对象,想要冻结对象就要使用Object.freeze()

// 创建foo常量 使用Object.freeze冻结
    const foo = Object.freeze({});

    // 常规模式时,下面一行不起作用;
    // 严格模式时,该行会报错
    foo.prop = 123;

        面对很复杂的对象时,如果想要彻底冻结它,可以遍历它的属性,判断它的属性类型,对它进行冻结。方法有很多,例如利用回调、简单的if判断等等...