读红宝书笔记 (为什么for循环使用var容易出错?)

233 阅读2分钟

最近在刷红宝书,希望自己可以坚持下去,偶尔写一篇笔记,以后来翻翻看 -v-

一道经典的面试题

   for(var i=0; i<5; i++){
      setTimeout(()=>{
         console.log(i)
      },0)
   };
   
   // 输出结果   5,5,5,5,5

在es6之前,定义变量都是用var,var有两种作用域,全局作用域、函数作用域。 以上代码相当于重复给全局作用域中的i赋值,最后导致循环结束的i是5,在异步代码(后面会提异步、同步)setTimeout执行的时候,输出的都是同一个i(5).

正常输出的解决办法有很多种

1.用let定义

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

解:es6之后有了let、const变量。这里把var换成let就可以了,因为let是有块级作用域的,以上每一次循环相当于:

    {
        let i = 0;
        setTimeout(()=>{ console.log(i) },0)
    } //第一次循环
    {
        let i = 1;
        setTimeout(()=>{ console.log(i) },0)
    }//第二次循环
    ...

当循环结束异步代码setTimeout执行的时候,取的是每个定时器所在作用域内的i,所以输出正常。

2.不写异步

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

我们把定时器删除也是可以正常输出的,因为输出的代码在每一次赋值后,也就是每一次循环立刻输出,代码是同步的。

同步异步:

js执行时如果遇到同步代码会立刻解释执行,遇到异步代码会放到异步任务队列,当同步代码执行完后会到异步任务队列中执行异步代码,所以面试题在循环5次都结束之后才会执行setTimeout中的代码,这时候i已经是5,所以打印结果都是5。

3.闭包

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

什么是闭包? 简单的回答是,当函数嵌套,内部函数使用了外部函数的变量,就会形成闭包。 闭包的优点:可以让变量长久的保存,在返回函数的情况下能让外部访问到函数内部的变量。 缺点:变量会常驻内存,逃避垃圾回收机制的回收,大量的使用闭包会造成堆栈溢出

总结:

在es6以后,var基本已经不会在被使用了,可完全被let取代,使用let、const编码更规范。
定义变量时用let,定义常量时用const,这样更利于代码维护及阅读。