event loop是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event Loop。
浏览器的Event Loop是在html5的规范中明确定义。
NodeJS的Event Loop是基于libuv实现的。可以参考Node的官方文档以及libuv的官方文档。
libuv已经对Event Loop做出了实现,而HTML5规范中只是定义了浏览器中Event Loop的模型,具体的实现留给了浏览器厂商。
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。
一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
script(整体代码),setTimeout,setInterval,setImmediate(Node独有),I/O,UI rendering。
process.nextTick(node独有),Promise,Object.observe(已废弃),MutationObserver(html5新特性)
setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。
其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。
浏览器的Event Loop完整流程图
JavaScript代码的具体流程:
执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
全局Script代码执行完毕后,调用栈Stack会清空;
主线程遇到异步任务,指给对应的异步进程进行处理(WEB API)
异步进程处理完毕(Ajax返回、DOM事件处罚、Timer到等),将相应的异步任务推入任务队列;
从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;
microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;
执行完毕后,调用栈Stack为空;
重复第3-7个步骤;
重复第3-7个步骤;
......
异步进程:
类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;
setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;
Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。
microtask、macrotask两者的区别:
microtask queue:唯一,整个事件循环当中,仅存在一个;执行为同步,同一个事件循环中的microtask会按队列顺序,串行执行完毕;
macrotask queue:不唯一,存在一定的优先级(用户I/O部分优先级更高)(未验证);异步执行,同一事件循环中,只执行一个。
这里归纳3个重点:
宏队列macrotask一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
微任务队列中所有的任务都会被依次取出来执行,直到microtask queue为空;
图中没有画UI rendering的节点,因为这个是由浏览器自行判断决定的,但是只要执行UI rendering,它的节点是在执行完所有的microtask之后,下一个macrotask之前,紧跟着执行UI render。
来举个简单的栗子:
console.log(‘script start 1‘); setTimeout(function() { console.log(‘setTimeout 2‘); }, 0); Promise.resolve().then(function() { console.log(‘promise1 3‘); }).then(function() { console.log(‘promise2 4‘); }); console.log(‘script end 5‘);
// script start 1 // script end 5 // promise1 3 // promise2 4 // setTimeout 2
再来一个栗子:
console.log(‘script start‘) async function async1() { await async2() console.log(‘async1 end‘) } async function async2() { console.log(‘async2 end‘) } async1() setTimeout(function() { console.log(‘setTimeout‘) }, 0) new Promise(resolve => { console.log(‘Promise‘) resolve() }) .then(function() { console.log(‘promise1‘) }) .then(function() { console.log(‘promise2‘) }) console.log(‘script end‘)
// script start // async2 end // Promise // script end // promise1 // promise2 // async1 end // setTimeout
这里需要先理解async/await
async/await 在底层转换成了 promise 和 then 回调函数。也就是说,这是 promise 的语法糖。每次我们使用 await, 解释器都创建一个 promise 对象,然后把剩下的 async 函数中的操作放到 then 回调函数中。
async/await的实现,离不开 Promise。从字面意思来理解,async是“异步”的简写,而 await 是 async wait 的简写可以认为是等待异步方法执行完成。
关于73以下版本和73版本的区别:
在老版本73版本以下,先执行promise1和promise2,再执行async1。
在73版本,先执行async1再执行promise1和promise2。
console.log(‘global start‘); setTimeout(function() { // 应该是这里执行前开始渲染ui,试试用alert阻塞下。 alert(‘ ui 已经渲染完毕了吗? ‘); console.log(‘timeout1‘); }) new Promise(function(resolve) { console.log(‘promise1‘); for(var i = 0; i < 1000; i++) { i == 99 && resolve(); } console.log(‘promise2‘); }).then(function() { console.log(‘then1‘); alert(‘ ui 开始渲染 ‘); }) div.innerHTML = ‘end‘; console.log(‘global end‘); // global start // promise1 // promise2 // then1 // 弹框: ui 已经渲染完毕了吗? // UI 渲染 (UI渲染在当前循环走完后执行) // 弹框: ui 已经渲染完毕了吗? // timeout1
再来一个稍微复杂一点的栗子
HTML: <div class="outer" style="width:200px;height:200px;background-color: #ccc"> <div class="inner" style="width:100px;height:100px;background-color: #ddd">begin</div> </div> JS: var outer = document.querySelector(‘.outer‘); var inner = document.querySelector(‘.inner‘); var i = 0; new MutationObserver(function() { console.log(‘mutate‘); }).observe(outer, { attributes: true }); function onClick() { i++; if(i === 1) { inner.innerHTML = ‘end‘; } console.log(‘click‘); setTimeout(function() { alert(‘锚点‘); console.log(‘timeout‘); }, 0); Promise.resolve().then(function() { console.log(‘promise‘); }); outer.setAttribute(‘data-random‘, Math.random()); } inner.addEventListener(‘click‘, onClick); outer.addEventListener(‘click‘, onClick); // click // promise // mutate // click // promise // mutate // 弹框:锚点 // timeout // 弹框:锚点 // timeout
如果是JS触发click呢 ?
inner.addEventListener(‘click‘, onClick); outer.addEventListener(‘click‘, onClick); inner.click(); // click // 由于js执行栈还没清空,仍有 inner.click()任务,所以不能执行微任务,继续冒泡 // click // promise // mutate // promise // 由于 上一个MutationObserver任务还未执行,不再添加第二个MutationObserver任务 // timeout
原文:https://www.cnblogs.com/chendewei/p/11928310.html