var,let和const的区别

139 阅读6分钟

前言

在讲var,let和const的区别之前,我们先来了解一下块级作用域

块作用域:
广义来讲:{}内就算一个块级作用域
所以if判断,for循环,function函数等这些有{}包围的地方就可以都算做块级作用域

1. var

var是用于用于声明一个全部范围或是函数范围的变量,并且可以为其初始化一个值。它是在ES5之前的变量声明的唯一关键字。

变量提升

我们在全局作用域或是局部作用域中,使用var关键字声明变量,都会被提升到该作用域的顶部

    function test() {
        if (false) {
            var value = 'hello'
        } else {
            console.log(value);
        }
        console.log(value);
    }

    test()

输出如下

image.png 在上述代码中,if代码块里面的代码都没有执行,为啥value还能获取到?
这就是因为var的变量提升,在JS的编译引擎中,在进行代码预编译时,会自动将所有代码里面的var关键字声明的语句都提升到当前的作用域的顶端,所以上述代码会被解析成下面这样:

    function test() {
        var value
        if (false) {
            var value = 'hello'
        } else {
            console.log(value);
        }
        console.log(value);
    }

    test()

在全局作用域下使用var声明一个变量,默认他是挂载在顶层对象window对象下(node是global)

        var value = 1
        console.log(window.value);

输出如下

image.png 用var声明的变量的作用域是它当前的执行上下文,可以是函数也可以是全局。

        var value = 1
        function test() {
            var value = 2
            console.log(value);
        }
        test()
        console.log(value);

输出如下

image.png 如果在函数中没有使用var声明变量,而是使用

        var value = 1
        function test() {
            value = 2
            console.log(value);
        }
        test()
        console.log(value);

输出如下

image.png 如果赋值给未声明的对象,当代码执行时,该变量会被隐式的创建为全局变量。

        a = 1
        console.log(window.a);
        function b() {
            b = 2
        }
        b()
        console.log(window.b);

输出如下

image.png

暂时性死区

var因为变量提升的原因,我们可以做到先使用再声明

for循环中使用var

var关键字平时在for循环中的使用与我们理解中的for循环使用可能有所出入,在普通的for循环中使用var定义变量,与我们正常使用for循环的输出一样,但当我们在for循环中出现了异步任务时,var关键字所定义的变量就会与我们日常使用的有所出入:

    for(var i=0;i<=5;i++){
        console.log(i);
    }
    console.log('------------');
    for(var i = 0;i<=5;i++){
        setTimeout(() => {
            console.log(i);
        }, 1000);
    }

上述代码,在我们的认知中,变量i当循环第一次执行时为0,随后进行判断,i是否小于等于5,再输出i,最后i++,我们所输出的i应该为0,1,2,3,4,5。可事实如此吗?

image.png 我们看代码输出,第一个for循环中,代码的输出与我们所认知的一模一样,但在第二个for循环中,缺输出的都是6,按常理来说,这是不科学的,为啥会输出都是6呢?
var关键字,所定义的变量缺少块级作用域,它只用全局作用域和函数作用域,在我们调用这个for循环时,var定义的i由于只有全局作用域或函数作用域,在第一个循环中,var定义的i只有全局作用域,他会在全局作用域中创建一个变量i,在输出中,输出的都是同一个i,因为每次都是先输出再i++,所以没有影响。 在第二个循环中就不一样了,它与第一个循环是一样的,但不同的是,循环中有一个setTimeout函数,setTimeout里面的是异步事件,for循环时同步事件,只有当同步事件执行完后才会执行异步事件。因为我们一直在调用的是同一个var,当同步事件结束后,var已经变成了6,此时才会执行异步事件,此时输出的i都为6。
为了解决这一问题,在ES6时,我们提出了新的定义变量关键字let

2. let

let是ES6中所提出来的新的一个声明变量的关键字,与var相比他做出了如下的变化:

  1. 存在块级作用域。
  2. 出现了暂时性死区。
  3. 变量不可重复声明。

块级作用域

在前言中,我们解释了何为块级作用域,var所声明的变量由于缺少块级作用域,在循环中使用,我们所获的i都是同一个,在普通循环中可能问题不大,但当我们在循环中引入异步任务后,就会出现很大的问题。在let关键字出来后,我们就可以解决这一问题了。

    function test() {
        let value = 5 
        if (true) {
            let value = 10
        }
        console.log(value);
    }
    test()

代码输出

image.png 上述代码中,在使用var来定义变量时,输出的value的值应该为10,但当使用let来定义变量时,输出的值为5,这由于let关键字,他所声明的变量仅在块作用域内有效
也就是说,test()函数中,存在两个块级作用域,两个块级作用域中各有一个value,这两个value的值毫无关系,不会互相影响。

暂时性死区

let关键字所定义的变量是否存在变量提升,众说纷纭,有的认为存在,有的就认为不存在,但无论let存不存在变量提升,它都无法做到像var一样,先使用后定义这种先上车后买票的流氓行为。在let中存在这暂时性死区,正是这一东西限制了let,让他无法做到先使用后定义

所谓的暂时性死区指的是:letconst这种变量声明所绑定的块级作用域的顶部,一直到变量声明语句执行完成之前,变量不能访问和修改的区域,称为这个变量的暂时性死区。

    console.log(a);
    let a = 1
    let b = 2
    console.log(b);

image.png 当我们在使用暂时性死区的时候,暂时性死区是基于执行顺序(时间)上的,而不是编写代码顺序(位置)上的。只要变量的访问和修改代码的执行,是在变量声明之后,就是合法的

不可重复声明

相比于var的重复声明会被忽略,let同一作用域中对同一标志符的变量不能重复声明。

image.png 这一约定,避免了同一作用域下不同位置的变量声明冲突,消除了许多不容易遇见的运行问题。

const

const的使用方式与let基本上上一样,但两个关键字最大的区别在于,const所声明的所声明的变量不能修改其指针,但我们可以修改它的值。
比如,我们定义一个对象,我们可以修改对象里面的属性,但我们不可以重写整个对象。

    const person ={
        name : 'zz',
        age : 22
    }
    person.name = 'z'
    console.log(person);
    person = {}
    console.log(person);

image.png

        const a = 1 
        a = 2
        console.log(a);

image.png

总结

varletconst三者最大的区别如下:

  1. var存在变量提升,可以重复声明变量,没有块级作用域
  2. let为了防止变量提升,出现了暂时性死区,出现了块级作用域,变量不能重复声明
  3. constlet的基础上,又增加了不能修改变量的指针