抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

一:什么是浏览器的事件循环机制?

  • 浏览器的事件循环是指在Web浏览器对事件的处理机制。它是基于异步编程模型,运行在JS的引擎之中

二:浏览器的进程和线程

首先要了解我们所编写的JJS代码最终是怎么在浏览器中执行的

浏览器工作原理(一):浏览器的进程与线程- zhangsai - 博客园

浏览器是一个复杂的应用程序,运行时候通常包含多个进程和多个线程,用于执行不同的任务和管理不同的资源

  • 进程:是操作系统进行资源的分配和调度的基本单位,进程是程序执行的基本实体
  • 线程:是操作系统能够进行运算的最小单位,一个进程中可以并发执行多个线程,每个线程并行执行不同的任务

浏览器的进程和线程

  • 渲染进程负责加载解析和渲染页面内容,每个标签页通常运行在独立的渲染进程中,每个渲染进程都包含多个线程

    • GUI线程:GUI主线程(也称为UI线程)是浏览器中负责处理用户界面操作和渲染的线程。它负责响应用户的交互操作、更新页面的显示等任务
    • JS引擎线程:负责解释执行JS代码,将JS代码转换成可执行指令,并按照指令的顺序进行执行
      • 在浏览器中,JS引擎的线程是单线程,一次只能执行一个任务,在执行JS代码时,用到了异步编程模型,通过事件循环机制处理异步任务,这样可以使得JS可以处理耗时的操作而不阻塞用户界面的相应
    • 事件监听线程:浏览器中负责监听和触发事件的线程。它是浏览器的一部分,用于处理用户输入、网络操作、定时器等事件的触发和相应。
    • 定时器线程负责管理和触发定时器相关的任务,如setTimeout和setInterval
    • 异步http请求线程:当发起AJAX请求时,浏览器会创建一个独立的线程来处理网络请求,该线程负责与服务器进行通信并接收响应
  • 插件进程负责控制网络使用的所有插件

  • GPU进程负责整个浏览器界面的渲染

三:同步和异步

JavaScript中,所有的任务都可以分为

  • 同步任务:同步任务会在GUI线程(主线程)和JS引擎线程中进行执行
    • GUI线程:主要负责页面的渲染,所以不能被长时间的同步代码阻塞
    • JS引擎线程:会阻碍其它任务的执行,包括GUI主线程的执行,如果JS引擎线程上的同步任务多的话,会导致页面的渲染和用户交互被暂时中断
  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout 定时函数等
    • 定时器线程
    • 异步http请求线程
    • 事件监听线程

三:宏任务和微任务

异步任务又分为宏任务和微任务

  • 宏任务:需要在事件循环中单独执行的任务单元
    • 渲染事件(如绘制页面、重新布局等)
    • 用户交互事件(如鼠标点击、键盘事件等)
    • 定时器事件(如 setTimeoutsetInterval 的回调函数)
    • 网络请求完成、文件读写完成等异步操作的回调函数
  • 微任务:在当前宏任务执行完毕后立即执行的任务单元
    • Promise 的回调函数
    • Object.Observer
    • MutaionObserver的回调函数

image-20230913210116708

四:事件循环机制

js原理之事件循环Event Loop 宏任务与微任务async与await - 知乎

  • 在事件循环中,调用栈和事件队列是两个重要的组成部分
    • 调用栈:当执行到JS代码时候,函数调用会被添加到调用栈中,按照先进后出的顺序执行
    • 任务队列:存储着待执行的异步任务,任务队列分为宏任务队列和微任务队列
      • 宏任务队列:存储着需要在事件循环中单独执行的任务,如定时器回调、事件回调等。
      • 微任务队列:存储着需要在当前宏任务执行完毕后立即执行的任务,如 Promise 的回调函数、MutationObserver 的回调函数等

事件循环流程

    1. 当执行同步代码的时候,函数调用会依次进入到调用栈中执行
    2. 当遇到异步任务时,定时器回调,定时器到期时,回调函数会进入到宏任务队列中等待执行
    3. 任务队列中的任务会等到调用栈为空时候,事件循环会从任务队列取出一个任务加入到调用栈中执行

注意

  • 微任务队列具有更高的优先级,会在下一个宏任务执行之前被处理。所以,当微任务队列不为空时,即使宏任务队列中有待执行的任务,也会先处理微任务队列中的任务。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(1)
setTimeout(() =>
{
console.log(2)
}, 0)
new Promise((resolve, reject) =>
{
console.log('new Promise')
resolve()
}).then(() =>
{
console.log('then')
})
console.log(3)

image-20230913211916706

  • 先执行同步代码,将console.log(1)压入调用栈中,执行完毕弹出,输出1
  • 遇到定时器,异步任务,交给定时器线程处理,等定时器到期将回调函数加入到宏任务队列
    • 宏任务队列:console.log(1)
  • new Promise是同步代码,直接执行,输出new Promise
  • 遇到.then异步代码,微任务,加入微任务队列
    • 微任务队列:console.log(‘then’)
  • 执行到console.log(3),同步代码直接执行,输出3
  • 同步代码全部执行完毕后,调用栈为空,要从任务队列中取出任务加入调用栈中
  • 由于微任务优先,所以从微任务队列中取出console.log(‘then’),并执行输出then
  • 再从宏任务队列中取出console.log(2),并执行输出2

评论