以for循环为例,彻底搞懂var和let的本质区别

270 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

从ECMAScript6后,我们多了let来申明变量,苦恼也随之而来,那到底该什么时候使用let,什么时候使用var呢?

简单粗暴地理解,var就是全局变量,let是局部变量,真的ok吗?那两个关键字申明的是不同类型的变量吗?关于var的变量提升,let的暂时性死区又该如何理解?

解决以上问题的关键就是明白var和let的本质,它们的根本区别在于声明变量的作用域上,let声明的变量块作用域(块级作用域就是{}),var声明的变量是函数作用域。

let的块作用域

{
     let nickname = "小Q"
     console.log(nickname);  // "小Q"
}
console.log(nickname);  // ReferenceError: nickname is not defined 

{
    var age = 1
    console.log(age);   // 1 
}
console.log(age);   // 1

let的作用域是块{},所以块级作用域之外打印nickname就报错了;这里的var申明的变量age是全局变量,所以没有问题。

var的函数作用域

下面我们来理解var的函数作用域:

function test() {
    var age = 1;    // 局部变量
}
test();
console.log(age)    // ReferenceError: age is not defined

var age;
function test() {
    age = 1;    // 全局变量
}
test();
console.log(age)    // 1

var age 在函数test内定义,此时age的作用域限制在test函数内部。调用test时,age被创建赋值;test函数调用结束后,age变量随即被销毁。

实践:for循环实例

下面我们通过for循环中的实例🌰,来加深理解。

for(var i=0; i<5; i++) {
    setTimeout(() => console.log(i), 0);
}
// 最后会输出5、5、5、5、5 

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

之所以会这样,是因为在第一个for循环中,var申明的迭代变量i,在退出循环后的终值是5,所以再执行定时器逻辑时,输出的都是同一个值5;

而在第二个for循环中,使用let申明迭代变量i后,js引擎会在后台为每次循环申明新的循环变量实例,所以在结束循环后,定时器中引用的都是不同的循环变量实例,输出为0、1、2、3、4;

单调的文字不好理解的话,下面我们还是写代码来解释一下:

// for循环 之let申明循环变量释义:
{
    let i = 0;

    {
        let i = i;   // 为每次循环申明新的循环变量实例i
        setTimeout(() => {
            console.log(i);   // 变量实例i的值为0
        }, 0);
    }

    i++;

    {
        let i = i;
        setTimeout(() => {
            console.log(i);   // 变量实例i的值为1
        }, 0);
    }

    i++;

    {
        let i = i;
        setTimeout(() => {
            console.log(i);   // 变量实例i的值为2
        }, 0);
    }

    i++;

    {
        let i = i;
        setTimeout(() => {
            console.log(i);   // 变量实例i的值为3
        }, 0);
    }

    i++;

    {
        let i = i;
        setTimeout(() => {
            console.log(i);   // 变量实例i的值为4
        }, 0);
    }
}

看到这,我想你也彻底明白了var和let的本质,那回过头来,我们再来回答下,前面的三个问题:

  1. 那两个关键字申明的是不同类型的变量吗?

    两个关键字申明的并不是不同类型的变量,它们的差别是在变量的作用域上。

  2. 关于var的变量提升,let的暂时性死区又该如何理解?

    所谓的var的变量提升,就是把所有var的声明都提升放到函数作用域的顶部。而let声明的范围是块级作用域,所以不可能检查前面是否已经使用let声明过同名变量,这时在let声明变量前调用let变量就会存在暂时性死区。

  3. 那到底该什么时候使用let,什么时候使用var呢?

    ECMAScript6新增了let,变量有了更明确的申明位置和作用域,尽量不再使用var,哈,这也是红宝书上的建议。

最后

文中若有我没说清楚或错误的地方,欢迎你们评论区留言呀!

反手赞一个吧!对我是莫大的鼓励 💗

参考文献

  1. [美]马特·弗里斯比:《JavaScript高级程序设计(第4版)》,李松峰译,人民邮电出版社2020年版。