- JS为什么是单线程的?
JS最初被设计用在浏览器中,那么想象一下,如果浏览器中的JS是多线程的。
场景描述:
那么现在有2个线程,process1 process2,由于是多线程的JS,所以他们对同一个dom,同时进行操作
process1 删除了该dom,而process2 编辑了该dom,同时下达2个矛盾的命令,浏览器究竟该如何执行呢?
这样想,JS为什么被设计成单线程应该就容易理解了吧。
- JS为什么需要异步?
场景描述:
如果JS中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。
对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验
- JS单线程又是如何实现异步的呢?
其实本质就是通过js的事件循环来实现的,也可以将此理解为js的执行机制
二、事件循环(Event Loop)
备注:js代码可以有两种分类,一种是分为同步代码和异步代码,还有一种更为精确的分类是分为宏任务和微任务,今天我们要讲的就是后一种。。。
刚提到了宏任务和微任务,那么哪些代码属于宏任务?哪些属于微任务呢?
宏任务:包括整体代码script,setTimeout,setInterval
微任务:Promise(then语句),process.nextTick
js的执行机制是:
- 开始执行js代码是,就是执行第一个宏任务,因为js代码就属于宏任务,然后按从上往下的顺序依次执行下去,当遇到像刚才提到的setTimeout,setInterval时,就会暂时先将他们整体的代码先放到宏任务的【事件队列】中,如果期间遇到像Promise(then语句),process.nextTick等微任务时,就将其放到微任务的【事件队列】里;
- 当前第一个宏任务执行完成后(js代码从上往下执行过一边,同步代码被执行完了,宏任务和微任务都被放到了各自的事件队列中),此时会查看微任务的【事件队列】,并将里面全部的微任务依次执行完,此时算是第一轮事件循环结束了。
总结:就是执行一个宏任务,把所有微任务执行完算一个循环,重复以上两个步骤,这就是js的事件循环,理解一点了吗?
好,接下来讲完了理论知识,我们就来进行一些实战练习,看一些例子巩固下:
Demo1:
console.log(1)
setTimeout(function(){
console.log(2)
},0)
console.log(3) //运行结果是:1,3,2
代码解析:
这个例子中,当执行第一句代码时,就进行了第一个宏任务的执行(普通js代码),所以按顺序从上往下执行,首先输出1,然后遇到setTimeout,将该段代码先暂时放到宏任务的事件队列中,然后接着往下执行,输出3,当第一轮的宏任务执行完成后,会去微任务的事件队列中去寻找有没有要执行的代码,很明显,此时没有,所以第一轮的事件循环结束,开始第二轮的事件循环,执行第二个宏任务(也就是刚才的setTimeout),于是最后输出的就是2,所以最终结果就是1,3,2
Demo2:
setTimeout(function(){
console.log('1')
});
new Promise(function(resolve){
console.log('2');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('3')
});
console.log('4');
代码解析:
这个例子中,同样也是一开始执行js代码时(第一个宏任务),遇到setTimeout,将其放至宏任务的事件队列中,Promise函数中的代码是同步立即执行的,所以此时第一个输出的是2,然后执行到then语句时,它属于微任务,所以将其放到微任务的事件队列中,遇到console.log(4)同步代码也是立即执行,到这第一个宏任务执行完成了,然后去微任务事件队列中去寻找有没有要执行的代码,也就是刚才的then语句,于是输出3,此时第一轮的事件循环结束了,开始执行第二个宏任务(setTimeout代码),于是输出1,所以最终这段代码的输出结果是2,4,3,1