likes
comments
collection
share

浏览器中的事件循环

作者站长头像
站长
· 阅读数 88

为什么出现事件循环

在了解浏览事件循环之前,我们首先要弄明白为什么会出现事件循环?为什么会产生消息队列?

第一版线程模型

  • 任务 1:1+2
  • 任务 2:20/5
  • 任务 3:7*8
  • 任务 4:打印出任务 1、任务 2、任务 3 的运算结果
void MainThread(){
     int num1 = 1+2; // 任务 1
     int num2 = 20/5; // 任务 2
     int num3 = 7*8; // 任务 3
     print(" 最终计算的值为:%d,%d,%d",num,num2,num3); // 任务 4
  }

当我们需要处理以上四个任务的时候,我们会把以上任务按照顺序写进主线程里,等线程执行时,这些任务会按照顺序在线程中依次被执行;等所有任务执行完成之后,线程会自动退出。

第二版线程模型(引入事件循环)

在任务处理过程中,不是所有任务都是提前准备好的。我们可能在任务执行的过程中,接受到一个新的任务,这时候我们就要引入事件循环,来监听是否有新的任务。

浏览器中的事件循环

现在的线程模型是这样

浏览器中的事件循环

这时候,我们就发现了一个问题。渲染进程会频繁的接受到IO线程的消息,就会造成页面渲染的卡顿。那么我们如何改进呢?

第三版线程模型(引入消息队列)

浏览器中的事件循环

经过改造之后,任务执行可分为三个步骤

  • 添加一个消息队列
  • IO线程会产生任务放进队尾
  • 渲染主进程会循环轮询消息队列

区分宏任务和微任务的必要性

了解了什么是时间循环之后,我们来谈谈为什么要有宏任务和微任务?

处理事件的优先级

比如当我们想要监听DOM元素的删除和修改等,当DOM元素发生变化的时候,我们应该是想立刻看到页面的变化。如果我们把DOM元素的优先级设置成最高,直接放到消息队列的队首,当操作DOM的时间很长时,其他任务就会在消息队列中长时间的等待,造成效率的降低。如果我们放到队尾,又会影响实时性,因为消息队列中可能有很多任务在等待中。

针对这种情况,宏任务和微任务就应运而生。我们将消息队列中的任务称为宏任务,每个宏任务在执行的过程中会产生相应的微任务队列。宏任务执行完成之后,渲染引擎并不会着急执行下一个宏任务,而是执行当前宏任务产生的微任务队列。

事件循环的执行过程

当我们大致了解了事件循环和宏任务,微任务之后,我们来了解一下事件循环具体的执行过程。

宏任务

(macro)task主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)

微任务

microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)

事件循环执行过程

浏览器中的事件循环

代码输出题

单独来讲的话太抽象了,我们直接来看几个代码输出题吧

题一

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')

首先,script(整体代码)是一个宏任务,先执行的是整体代码,先输出 'script start' ,然后再执行new Promise里面executor函数, 输出 'promise1' ,再输出 'promise1 end' 然后执行resolve()。 然后将then()里面的函数添加到微任务队列中,再执行setTimeout,并将添加到下一个宏任务队列里面。 此时, script整体代码(宏任务)执行完毕,然后再执行微任务队列,输出 'promise2' 。此时微任务执行完毕, 执行下一个宏任务队列, 输出 'settimeout'。

浏览器中的事件循环

题二

在看题二之前,我们再复习一下Promise

resolve(参数)有以下几种情况

  • 1.普通的值或者对象
  • 2.传入一个promise,那么当前Promise的状态由传入的Promise决定,相当于状态进行了移交
  • 3.传入一个对象,并且这个对象中有then方法(thenable),那么也会执行该then方法,并且由该then方法决定后续状态

第二种情况,传递一个promise,promise的状态由传入的newPromise的状态决定

const newPromise = new Promise((resolve,reject) => {
    // resolve('aaa');
    // reject('err')
})

new Promise((resolve, reject) => {
    resolve(newPromise)
}).then(res => {
    console.log(res)
},err => {
    console.log(err)
})

第三种情况,传入对象中有then方法

new Promise((resolve,reject) => {
    const obj = {
        then:function (resolve,reject) {
            reject('err')
        }
    }
    resolve(obj)
}).then((res)=> {
    console.log('res',res)
},err => {
    console.log('err',err)
})

// 输出 err err

好了,我们可以正式来看第二题了

new Promise(resolve => {
    resolve(1);
    Promise.resolve().then(() => console.log(2));
    console.log(4)
}).then(t => console.log(t));

console.log(3);

都看到这里了,相信大家肯定是知道先输出4 3 ,那么2 1是谁先输出呢?

在阮一峰老师的Es6中,Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的Promise 对象。

需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。 es6.ruanyifeng.com/#docs/promi…

这段代码的流程大致如下:

  1. script 任务先运行。首先遇到 Promise 实例,构造函数首先执行,所以首先输出了 4。此时 microtask 的任务有 t2 和 t1
  2. script 任务继续运行,输出 3。至此,第一个宏任务执行完成。
  3. 执行所有的微任务,先后取出 t2 和 t1,分别输出 2 和 1
  4. 代码执行完毕

综上,上述代码的输出是:4321

为什么 t2 会先执行呢?理由如下:

实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行

所以,t2 比 t1 会先进入 microtask 的 Promise 队列。

自己思考一下撒

setTimeout(()=>{
    console.log('setTimeout')
},0)

Promise.resolve().then(()=>{
    console.log('promise1')
    Promise.resolve().then(() => {
        console.log('promise2')
    })
})

console.log('main')
let thenable = {
    then: function(resolve, reject) {
        console.log(0)
        resolve(42);
    }
};
new Promise(resolve => {
    resolve(1);

    Promise.resolve(thenable).then((t) => {

        console.log(t)
    });
    console.log(4)
}).then(t => {

    console.log(t)
});
console.log(3);

自己看了些资料然后总结了一下事件循环,如果有不对的地方欢迎大家一起交流哈!!!

参考文档

从一道题浅说 JavaScript 的事件循环 · Issue #61 · dwqs/blog (github.com)

前端工程师一定要懂哪些浏览器原理?-极客时间 (geekbang.org)

转载自:https://juejin.cn/post/7156450332757721119
评论
请登录