JS函数作用域

156 阅读2分钟

JS函数作用域

先来看一段经典代码

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

为什么不是 0 1 2 3 4 5呢?

原因在于js的事件循环机制。js的执行环境是单线程的,也就是一次只能做一件事情,这就造成了一下问题:如果某个函数执行太慢或者某个请求由于网络原因迟迟未响应,由于单线程的机制,后续的任务就要一直等待前面的任务完成,就会造成整个页面卡死。于是js使用了同步和异步结合的执行模式,不耗时间,不需要等待的代码一个一个同步执行,而需要花费大量时间执行的代码可以通过先加入一个任务队列,等到同步代码执行完毕后,再以回调函数的方式调用执行。这就是所谓的事件循环。

上面的代码中,settimeout是回调函数,在指定时间结束后回调,其余代码均为同步代码,因此它的执行顺序是:for循环一直运行到循环结束,此时i=6,再从任务队列中取出6次settimeout回调函数执行,故打印的均为6。

解决方法

解决上述问题的代码很简单,使用es6的let即可。let拥有块级作用域,for循环内部的块级作用域会在每个循环中复制一份i,复制的值即当时的值,因此这时的settimeout调用的是块级作用域中i的6个复制品。

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

其他解决方法:

1.闭包 采用立即执行函数或其他方式构成闭包,可获取i的副本当做参数传入闭包

for(var i=0;i<=6;i++){ 
(function(j){ 
setTimeout(function timer(console.log(j)
},0) 
})(i)
}
  1. setTimeout的第三个参数 setTimeout的第三个参数当做一个实参传入执行函数
for(var i=0;i<6;i++){
    setTimeout(function(j){
        console.log(j);
    },0,i);
}

再看一段代码

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

这是怎么回事?不是说let有块级作用域吗,为什么还是6?

请注意,这里的let是定义在for循环外部的,此时的let相当于在for循环外部的块级作用域中定义的,而不是for循环内部的块级作用域中。for循环内部定义的let会在每次循环产生的块级作用域中定义一个i,所以每次循环的i才不相同。而外部定义时,for循环只是一直在引用同一个i变量,因此每次i++时都会影响i的值,导致for循环结束后,settimeout引用的i已经变成6了。

{ //相当于最外层有一个块级作用域,只有一个i变量
let i 
for( i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}
}