事件循环详解 (2)

js 引擎遇到上文所列的异步任务后,会交个相应的线程去维护异步任务,等待某个时机,然后由事件触发线程将异步任务对应的回调函数加入到事件队列中,事件队列中的函数等待被执行。

js 引擎在执行过程中,遇到同步任务,会将任务直接压入执行栈中执行,当执行栈为空(即 js 引擎线程空闲),事件触发线程会从事件队列中取出一个任务(即异步任务的回调函数)放入执行在栈中执行。

执行完了之后,执行栈再次为空,事件触发线程会重复上一步的操作,再从事件队列中取出一个消息,这种机制就被称为事件循环(Event Loop)机制。

为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)。

例子代码:

console.log('script start') setTimeout(() => { console.log('timer 1 over') }, 1000) setTimeout(() => { console.log('timer 2 over') }, 0) console.log('script end') // script start // script end // timer 2 over // timer 1 over

模拟 js 引擎对其执行过程:

第一轮事件循环:

console.log 为同步任务,入栈,打印“script start”。出栈。

setTimeout 为异步任务,入栈,交给定时器触发线程处理(在1秒后加入将回调加入事件队列)。出栈。

setTimeout 为异步任务,入栈,交给定时器触发线程处理(在4ms之内将回调加入事件队列)。出栈。

console.log 为同步任务,入栈,打印"script end"。出栈。

此时,执行栈为空,js 引擎线程空闲。便从事件队列中读取任务,此时队列如下:

第二轮事件循环

js 引擎线程从事件对列中读取 cb2 加入执行栈并执行,打印”time 2 over“。出栈。

第三轮事件循环

js 引擎从事件队列中读取 cb1 加入执行栈中并执行,打印”time 1 over“ 。出栈。

注意点:

上面,timer 2 的延时为 0ms,HTML5标准规定 setTimeout 第二个参数不得小于4(不同浏览器最小值会不一样),不足会自动增加,所以 "timer 2 over" 还是会在 "script end" 之后。

就算延时为0ms,只是 time 2 的回调函数会立即加入事件队列而已,回调的执行还是得等到执行栈为空时执行。

四、宏任务 & 微任务

在 ES6 新增 Promise 处理异步后,js 执行引擎的处理过程又发生了新的变化。

看代码:

console.log('script start') setTimeout(function() { console.log('timer over') }, 0) Promise.resolve().then(function() { console.log('promise1') }).then(function() { console.log('promise2') }) console.log('script end') // script start // script end // promise1 // promise2 // timer over

这里又新增了两个新的概念,macrotask (宏任务)和 microtask(微任务)。

所有的任务都划分到宏任务和微任务下:

macrotask: script 主代码块、setTimeout、setInterval、requestAnimationFrame、node 中的setimmediate 等。

microtask: Promise.then catch finally、MutationObserver、node 中的process.nextTick 等。

js 引擎首先执行主代码块。

执行栈每次执行的代码就是一个宏任务,包括任务队列(宏任务队列)中的。执行栈中的任务执行完毕后,js 引擎会从宏任务队列中去添加任务到执行栈中,即同样是事件循环的机制。

当在执行宏任务遇到微任务 Promise.then 时,会创建一个微任务,并加入到微任务队列中的队尾。

微任务是在宏任务执行的时候创建的,而在下一个宏任务执行之前,浏览器会对页面重新渲染(task >> render >> task(任务队列中读取))。同时,在上一个宏任务执行完成后,页面渲染之前,会执行当前微任务队列中的所有微任务。

所以上述代码的执行过程就可以解释了。

js 引擎执行 promise.then 时,promise1、promise2 被认为是两个微任务按照代码的先后顺序被加入到微任务队列中,script end执行后,栈空。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspyyg.html