1. 先看一个例子
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
结果打印出6个6
这是什么原因呢?说一说我的理解
- let声明的变量是块级作用域,作用域在for循环的上一级
- for循环执行完毕后,才会执行setTimeout中的函数
- 由于每次for循环都会覆盖上一次的值,所有最后i会在声明的作用域中找到它的值,为6
- 由于循环了6次,访问到的i都是同一个,所以会打印出6个6
2. 写一个方法打印出不同以上的结果呢
只需要将i声明放在for循环里面
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
结果:0、1、2、3、4、5
发现没有, 仅仅改变 let 的位置, 当 for 和 let 结合,就能有如此奇效, 这就是 JS 给小白的爱。
那还有没有别的办法, 在 ES6 之前,还真有另外的两种方法,一种是利用 setTimeout 的第三个参数,另外一种是利用闭包。
3. 另外两种方法
// 第一种方法
for (var i=0; i<6; ++i) {
setTimeout(function(i){
console.log(i)
}, 0, i)
}
// 第二种方法
for(var i=0; i<6; ++i) {
!(function(j) {
setTimeout(function(){
console.log(j)
}, 0)
})(i)
}
任务队列
要实现非阻塞,主要靠异步,怎么实现呢,需要有一个静态的任务队列,存储异步处理完毕后返回的回调函数。
同步任务
在主线程上排队执行的任务,常见的有:
- 输出,如
console.log() - 变量声明
- 同步函数,也就是被调用时不会立即返回,而是等函数内所有任务都做完了再返回的函数。
异步任务
在异步线程上执行的任务,比如setTimeout,常见的还有AJAX等。
工作原理
一开始主线程执行console.log(1),紧接着检测到setTimeout()函数,把它移交给响应的异步线程处理,之后主线程就跳过setTimeout(),继续执行下面的console.log(3);一开始任务队列是空的,主线程中的setTimeout()进入异步线程后,开始执行,经过1秒钟的延迟后,setTimeout()的回调函数进入任务队列,主线程的同步任务执行完毕后,进入了空闲期,于是开始询问任务队列是否有任务需要主线程完成,此时任务队列里存在之前的setTimeout()运行完毕后的回调函数,这个函数被取出到主线程执行,于是回调函数中的console.log(2)内容被执行。(主线程、异步线程和任务队列中其实还有一个中介人叫轮询处理线程Event Loop,为了简化描述这里省略了)。