Todo List
- 为什么js在浏览器中有事件循环的机制
- 你了解事件循环中的两种任务么?
- 为什么要引入微任务的概念,只有宏任务可以么?
- 描述一下浏览器中事件循环的具体机制执行流程
- Node中的事件循环和浏览器中的事件循环有什么区别
- async / await
- 做题
- 进阶:伪代码实现
为什么JS在浏览器中有事件循环的机制
? one thread 单线程
= one call stack 一个调用栈
= one thing at a time 同一时间只能做一件事
答:
JS是单线程的,单线程意味着同一时间只能做一件事情,当前的事情没有做完时线程就会被挂起,造成后续任务的阻塞。 比如一些耗时的脚本下载,它不应该对用户后续的行为造成阻塞。于是就提出了异步任务(回调函数)的概念,将一些需要时间的事件交给处理异步任务的线程管理,不对主线程执行后续任务造成阻塞。
因为增加了多个线程并行之后,主线程需要知道其他线程的工作状态,“xx任务是否开始?xx任务是否执行完成?是否有异常情况?”等等。设想一下,如果有两个线程同时在操作一个dom节点,一个线程在删除dom节点,一个线程在操作dom节点,这两个操作就是互相冲突的,对于浏览器而言就会得不到我们想要的正确渲染结果。
主线程需要频繁的和多个线程协调任务、调度任务,于是浏览器又进一步引入了事件循环(EventLoop)的机制,来协调多个线程多个事件之间的工作。
你了解事件循环中的两种任务么?
? 事件循环机制将异步任务分为了两种类型,宏任务 + 微任务
答:
宏任务:诸如 整个script
、各种事件回调(dom 事件、I/O)
、setTimeout
、setInterval
等任务。
微任务:诸如 Promise.[then/finally/catch]
、MutationObserver (dom变化监听/前端回溯)
等任务。
为什么要引入微任务的概念,只有宏任务可以么?
答:
不可以,为了解决单线程的同步阻塞问题,引入了异步任务,并有多个线程协作。又为了更好的调度任务、协调多个线程之间的工作,引入了事件循环机制。
因为将耗时的任务以异步任务的方式交给了其他线程处理,而为了保证多线程之间的有序工作,事件循环机制下只有主线程执行完调用栈内当前的所有同步任务,才会去询问有哪些异步任务可以传回主线程执行。
也就是说无论你的异步任务实际耗不耗时,也一定至少是下一轮事件循环(一个用来调度主线程与其他线程之间任务的机制),它的回调函数才会被推入调用栈执行。而这样就会影响到需要尽快处理的那些事件,因为你不知道你前面还排着多少异步任务回调未被推入调用栈。于是就此引入了微任务的概念,去处理异步任务里追求时效性、更高优先级的事情。
微任务的回调函数会在当前次事件循环结束前被推入调用栈执行。也就是说虽然是同一轮遇到的宏任务和微任务,但是宏任务的回调们会被放到后续的事件循环中执行,而微任务的回调们会在这一次事件循环结束前被全部执行。
描述一下浏览器中事件循环的具体机制执行流程
答:
[精简版]:
调用栈为空 -> 执行宏任务队列中最早的一个宏任务 x
-> 执行 x
关联的微任务队列中的所有微任务 -> 调用栈为空 -> .... (重复如上动作) 这样重复的轮询机制,被称之为事件循环。
[叙述版]:
理解代码运行过程
加载JS,遇到任务(函数调用)会被推入调用栈,调用栈内的任务由主线程执行,执行完成后出栈,调用栈追踪JS的整个执行过程。
同步任务会直接在调用栈内完成执行,异步任务会在入栈后传递给其他线程处理,对应异步任务函数出栈。其他线程处理异步任务时,当这些任务满足了执行回调的条件,按照任务类型,分别塞入宏任务队列和当前宏任务关联的微任务队列。等待被推入调用栈执行。
代码如下:
function test() {
console.log(1)
}
console.log(2)
test()
执行过程用简单的动画模拟了一下:
多线程之间的资源协调、任务调度
事件循环会持续不断的去监听调用栈,当调用栈空闲时(无可执行的任务,调用栈里只有全局执行环境),会读取宏任务队列中最早的一个任务,推入调用栈执行(运行流程同 1)。
在当前宏任务内各任务(函数)均执行完成,即当前批宏任务结束前,读取当前宏任务关联的微任务队列中的任务,依次推入调用栈执行(运行流程同 1)。
调用栈空闲,推入最早的一个宏任务 -> 宏任务内部任务执行完成 -> 关联微任务队列全部执行完毕,这一连动作都做完调用栈又回到了空闲状态,这就是一轮事件循环。当JS引擎监听到调用栈空闲,并且宏任务队列上还有可以执行的任务,就会开启新一轮的事件循环,就这样一直重复轮询。
Node中的事件循环和浏览器中的事件循环有什么区别
Node v10及以前:node.js 将事件循环机制分为了6个阶段,根据宏任务的作用,会分到不同阶段处理。而和浏览器事件循环机制不相同的是 —— v10 之前的node.js,在每一个阶段的所有宏任务全执行完成后,才会去查询一次这个阶段宏任务相关的所有微任务,依次执行。
Node v10以后:在 node.js v10 之后的事件循环机制和浏览器事件循环机制流程保持了统一,在一个宏任务内所有任务执行完成后(整个宏任务结束前),就会去执行与之相关联的所有微任务,然后再开始下一个宏任务。
如下代码运行后结果:
setTimeout(() => {
console.log(1)
Promise.resolve().then(() => console.log(2))
}, 0)
setTimeout(() => {
console.log(3)
Promise.resolve().then(() => console.log(4))
}, 0)
Node v10 及以前:1 -> 3 -> 2 -> 4
Node v10 以后:1 -> 2 -> 3 -> 4
做题
async / await
讲到主线程和调用栈,就有一个不得不提的内容 async / await
async
函数会返回一个 Promise
对象,便于回调函数管理(支持链式)。
await
是一个运算符,用于组成表达式,await xxx
的计算结果取决于 await
它等待的东西,也就是 xxx
。如果它等待的不是一个 Promise
,那么它的计算结果就是它等待的东西。
当 await
等待的是一个 Promise
时,它会对当前 await
后面声明的代码进行阻塞,直到 Promise
返回后才会继续执行后续代码。如果 xxx
遇到了 error 且没有做异常捕获的话,那 await
之后的内容永远不会被执行。
执行流遇到 await functionXX(): Promise<any>
时,因为发生了函数调用,所以functionXX
会被压入调用栈,执行流会进入到函数内部,将 return Promise
之外的代码执行一遍(同步任务),Promise相关的异步操作会交由浏览器执行。
- 如下代码运行后结果
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(function (resolve) {
console.log('[ promise1 ]')
resolve()
}).then(function () {
console.log('[ promise2 ]')
})
console.log('[ script end ]')
答:
// 待补充
- 如下代码运行后结果
console.log(1)
// 1s 延时
setTimeout(() => {
console.log(2)
}, 1000)
async function fn() {
console.log(3)
setTimeout(() => {
console.log(4)
}, 20)
return Promise.reject()
}
async function run() {
console.log(5)
await fn()
console.log(6)
}
run()
//需要执行 150ms 左右
for (let i = 0; i < 90000000; i++) {}
setTimeout(() => {
console.log(7)
new Promise((resolve) => {
console.log(8)
resolve()
}).then(() => {
console.log(9)
})
}, 0)
console.log(10)
答:
// 待补充
- 如下代码运行后结果
function executor(resolve, reject) {
let rand = Math.random()
console.log(1)
if (rand > 0.5) resolve()
reject()
}
const p0 = new Promise(executor)
const p1 = p0.then((_) => {
console.log('success-0')
return new Promise(executor)
})
const p2 = p1.then((_) => {
console.log('success-1')
return new Promise(executor)
})
p2.catch((_) => {
console.log('error')
})
console.log(2)
答:
// 待补充
结束语
感谢阅读 ?????? / 感谢阅读 ?????? / 感谢阅读 ??????
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!