首页 > Web开发 > 详细

JS 的 Event Loop机制理解

时间:2019-11-25 16:33:23      阅读:92      评论:0      收藏:0      [点我收藏+]

JS 的 Event Loop机制理解

Event Loop是什么?

event loop是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event Loop。

  1. 浏览器的Event Loop是在html5的规范中明确定义。

  2. NodeJS的Event Loop是基于libuv实现的。可以参考Node的官方文档以及libuv的官方文档。

  3. libuv已经对Event Loop做出了实现,而HTML5规范中只是定义了浏览器中Event Loop的模型,具体的实现留给了浏览器厂商。

宏任务和微任务

  JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。

一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

宏任务macro-task

  script(整体代码),setTimeout,setInterval,setImmediate(Node独有),I/O,UI rendering。

微任务micro-task

  process.nextTick(node独有),Promise,Object.observe(已废弃),MutationObserver(html5新特性)

  1. setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。

  2. 来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。

  3. 其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。

浏览器的Event Loop

技术分享图片

 

 

技术分享图片

 

 

浏览器的Event Loop完整流程图

JavaScript代码的具体流程:

  1. 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);

  2. 全局Script代码执行完毕后,调用栈Stack会清空;

    1. 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API)

    2. 异步进程处理完毕(Ajax返回、DOM事件处罚、Timer到等),将相应的异步任务推入任务队列;

  3. 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;

  4. 继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;

  5. microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;

  6. 取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;

  7. 执行完毕后,调用栈Stack为空;

  8. 重复第3-7个步骤;

  9. 重复第3-7个步骤;

  10. ......

异步进程:

  1. 类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;

  2. setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;

  3. Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。

microtask、macrotask两者的区别:

microtask queue:唯一,整个事件循环当中,仅存在一个;执行为同步,同一个事件循环中的microtask会按队列顺序,串行执行完毕;

macrotask queue:不唯一,存在一定的优先级(用户I/O部分优先级更高)(未验证);异步执行,同一事件循环中,只执行一个。

 

这里归纳3个重点:

  1. 宏队列macrotask一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;

  2. 微任务队列中所有的任务都会被依次取出来执行,直到microtask queue为空;

  3. 图中没有画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。

那么整个事件循环中何时进行ui render呢?

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

 


 

 

JS 的 Event Loop机制理解

原文:https://www.cnblogs.com/chendewei/p/11928310.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!