浏览器中的事件循环

简介: 浏览器中的事件循环

为什么出现事件循环



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


第一版线程模型


  • 任务 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
  }


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


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


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

image.png

现在的线程模型是这样

image.png

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


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


image.png

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

  • 添加一个消息队列
  • 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 环境)


事件循环执行过程


image.png


代码输出题


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


题一


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'。

image.png


题二


在看题二之前,我们再复习一下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 的任务有 t2t1
  2. script 任务继续运行,输出 3。至此,第一个宏任务执行完成。
  3. 执行所有的微任务,先后取出 t2t1,分别输出 2 和 1
  4. 代码执行完毕


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


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


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


所以,t2t1 会先进入 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)



目录
相关文章
|
1月前
|
存储 JavaScript 网络协议
浏览器与 Node 的事件循环
浏览器和Node.js的事件循环是异步操作的核心机制。它们通过管理任务队列和回调函数,确保程序在处理耗时任务时不会阻塞主线程,从而实现高效、响应式的应用开发。
|
7月前
|
Web App开发 JavaScript 前端开发
浏览器与Node.js事件循环:异同点及工作原理
浏览器与Node.js事件循环:异同点及工作原理
|
5月前
|
存储 JavaScript 前端开发
在?聊聊浏览器事件循环机制
在?聊聊浏览器事件循环机制
50 0
|
7月前
|
存储 前端开发 JavaScript
揭秘浏览器的事件循环:让网页动起来的幕后英雄
揭秘浏览器的事件循环:让网页动起来的幕后英雄
揭秘浏览器的事件循环:让网页动起来的幕后英雄
|
JavaScript 前端开发
浏览器中的事件循环和Node.js中事件循环的区别(经典面试题)
浏览器中的事件循环和Node.js中事件循环的区别(经典面试题)
807 0
|
移动开发 前端开发 HTML5
浏览器和 Node 中的事件循环有什么区别?
浏览器和 Node 中的事件循环有什么区别?
102 0
|
消息中间件 Web App开发 监控
浏览器原理 14 # 消息队列和事件循环
浏览器原理 14 # 消息队列和事件循环
211 0
浏览器原理 14 # 消息队列和事件循环
|
JavaScript 前端开发 API
JavaScript 事件循环(EventLoop) —— 浏览器 & Node
JavaScript 事件循环(EventLoop) —— 浏览器 & Node
115 0
|
存储 Web App开发 缓存
浏览器专题系列 - 事件循环机制
浏览器专题系列 - 事件循环机制
|
消息中间件 Web App开发 存储
浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务
关键词:多进程、单线程、事件循环、消息队列、宏任务、微任务
浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务