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执行后,栈空。