对JS事件循环的理解
单线程
所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。
JS中的单线程是指只有一个主线程(JS引擎线程),但是他的宿主(Node、浏览器)是多线程的。
一个简单的例子,setTimeout,如果计时器在主线程上计时,那将会阻塞后面的代码,那么是怎么实现计时的?
这个操作会交给专门处理计时器的线程,这个线程由宿主提供,计时完成后,会通知主线程,然后执行回调。
一些常见的线程:
JS引擎线程(主线程)计时器线程GUI渲染线程事件触发线程HTTP请求线程
主线程和渲染线程是互斥的,当主线程执行完后,才会执行渲染线程。
例子:点击按钮,延迟一秒后背景颜色才改变。这里抛出一个问题,如何让主线程和渲染线程独立执行。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
</head>
<body>
<button onclick="test()">test</button>
</body>
<script>
function test() {
document.getElementsByTagName('body')[0].style.background = 'blue';
let start = new Date()
while (true)
if (new Date() - start >= 2000) return;
}
</script>
</html>宏任务和微任务
先看一段代码,运行环境:node v14.15.0
console.log('主线程代码开始')
setTimeout(() => {
console.log('setTimeout1')
})
new Promise(resolve => {
console.log('promise1');
resolve()
}).then(() => { console.log('promise then1') })
setImmediate(() => {
console.log('setImmediate1')
})
setTimeout(() => {
console.log('setTimeout2')
})
setImmediate(() => {
console.log('setImmediate2')
})
process.nextTick(() => {
console.log('nextTick')
})
new Promise(resolve => {
console.log('promise2');
resolve()
}).then(() => { console.log('promise then2') })
console.log('主线程代码结束')结果:可以看到执行顺序为promise pending > nextTick > promise resolve > setTimeout > setImmediate
主线程代码开始
promise1
promise2
主线程代码结束
nextTick
promise then1
promise then2
setTimeout1
setTimeout2
setImmediate1
setImmediate2JS引擎遇到同步代码会执行,异步代码会放进任务队列,等主线程上的同步代码执行完后再从任务队列中取出执行。
异步任务中又分为宏任务(macrotask)和微任务(microtask),宏任务由宿主发起,微任务由JS引擎发起。
宏任务:
- script
- setTimeout/setInterval
- requestAnimationFrame
- UI rendering/UI事件
- setImmediate、IO(Node)
- postMessage
微任务:
- process.nextTick(Node)
- MutationObserver
- Promise
- Object.observe(已废弃;
Proxy对象替代)
个人对事件循环的理解:执行同步代码 > 执行微任务 > 执行宏任务 > 进入下一个循环
大多数情况下setTimeout会在setImmediate之前执行,在IO操作中setImmediate会在setTimeout之前执行。
需要了解这个问题的原因,还需要了解Node的事件驱动,先挖个坑。
参考文章:
https://www.cnblogs.com/ckAng/p/11133643.html
https://www.jianshu.com/p/bfc3e319a96b
http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
https://www.jianshu.com/p/184988903562
https://www.jianshu.com/p/8821c6432fe1
https://blog.csdn.net/wu_xianqiang/article/details/105837869