事件循环机制(EventLoop)
小伙伴们大家好,今天分享一篇关于事件循环机制的文章,事件循环可以说是面试中的必问问题了, 了解事件循环可以帮助我们更深刻的理解JS执行逻辑,那么话不多说,来看看事件JS的事件循环到底是什么样的吧.
JS为什么是单线程?
首先为什么会有事件循环? 因为JS是单线程的, 那JS为什么是单线程呢, 明明其他语言都可以做到多线程运行为什么JS不行?
其实JS是在浏览器中运行渲染的, 由于浏览器是多线程的, JS如果也是多线程, 那么在浏览器运行当前JS时创建了两个线程 process1 和 procress2, 此时JS对同时对一个DOM元素进行操作, 一个线程创建DOM,一个线程删除DOM, 这两个命令矛盾了, 此时浏览器该如何执行呢? 所以JS被设计成了单线程, 所以才有了事件循环机制
单线程的JS为什么需要异步
如果JS中不存在异步, 只能自上至下执行, 那么如果一行代码执行了很长的时间, 下面的代码就会被阻塞, 这样就导致用户会有一个很差的使用体验, 所以JS中存在异步执行
什么是事件循环?
事件循环我们可以理解为是JavaScript或者Node为了解决单线程执行代码不阻塞主进程的一种机制,也就是JavaScript的异步原理
什么是任务队列?
JavaScript语言将任务分为两种模式执行分别为同步任务和异步任务,当JavaScript代码执行时, 同步和异步任务会进入到不同的场所等待执行,同步任务会进入到主线程中, 异步任务则会进入到Event Table并注册回调函数, 当满足了触发条件之后在被推入Event Queue, 主线程中的任务先执行,当主线程任务全部执行完毕后, 执行栈的任务为空时, 就会读取任务队列(Event Queue)中的事件, 去执行对应的回调函数.
宏任务、微任务
在异步任务中,又分为宏任务和微任务, 他们都属于耗时任务
| 宏任务 | 微任务 |
|---|---|
| 整体的script代码 | Promise(then. catch, finally...) |
| 计时器(setTimeout, setInterval) | async, await |
| AJAX请求 | Object.observe(用来实时监测JS中对象的变化) |
| i/o操作(输入输出, 比如读取文件操作、网路请求) | MutationObserver(监听DOM树变化) |
| ui render(dom渲染) | process.nextTick |
异步任务执行顺序
上面提到了js代码执行时会先执行同步代码,同步代码执行完执行异步,那异步代码的执行顺序是什么呢? 首先异步任务中,遇到异步宏任务放到宏任务队列中,遇到微任务则放到微任务队列, 当所有同步代码执行完毕后,将所有微任务从任务队列推到执行栈中执行,所有微任务执行完毕后在将异步宏任务调入到执行栈执行,一直循环至所有的任务执行完毕(完成一次事件循环Event Loop)
案例练习
setTimeout(function () {
console.log('timeout1')
}, 1000)
console.log('start')
Promise.resolve().then(function () {
console.log('promise1')
Promise.resolve().then(function () {
console.log('promise2')
})
setTimeout(function () {
Primise.resolve().then(function () {
console.log('promise3')
})
console.log('timeout2')
}, 10)
})
console.log('done')
解析:
- 按顺序执行, setTImeout宏任务, 放到宏任务队列, 记为time1
- console.log('start')为同步任务直接在执行栈中执行,输出start
- Promise.then为微任务, 进入微任务队列
- 按顺序执行,执行同步任务console.log('done'), 输出done
- 同步任务全部执行完毕, 执行异步任务, 先执行异步微任务, 在Promise中继续按照有同步先执行同步没有同步任务执行微任务的顺序执行, 所以先输出promise1, 然后执行promise.then(),输出promise2, 最终执行宏任务setTimeout, 先输出同步任务timeout2,在输出promise3
- 最后执行之前标记为time1的计时器,输出timeout1
输出结果: start, done, promise1, promise2, timeout2, promise3, timeout1
async function async1 () {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise(resolve => {
console.log('promise0')
resolve()
})
.then(() => {
console.log('promise1')
})
.then(() => {
console.log('promise2')
})
console.log('script end')
解析:
- 按顺序执行, anync1 async2都是微任务进入到微任务队列, 执行同步任务输出script start
- 计时器记为time1, 执行async1()
- 函数async1执行, 输出async1 start, 然后执行await async2(), 输出async2, 因为await等待一个成功的promise对象, async2中没有返回成功的promise对象, 所以会阻塞await下面的代码执行
- 继续向下执行, 输出promise0, 以及script end
- 执行promise.then中的任务输出 promise1, promise2
- 微任务全部执行完毕后执行async1中未执行完的代码输出async1 end
- 最后执行time1, 输出setTimeout
输出结果: script start, async1 start, async2, promise0, script end, promise1, promise2, async1 end, setTimeout