一:什么是浏览器的事件循环机制?
- 浏览器的事件循环是指在
Web浏览器
中对事件的处理机制
。它是基于异步编程模型,运行在JS的引擎之中
二:浏览器的进程和线程
首先要了解我们所编写的J
JS
代码最终是怎么在浏览器中执行的
浏览器是一个复杂的应用程序,运行时候通常包含多个
进程
和多个线程
,用于执行不同的任务和管理不同的资源
- 进程:是操作系统进行资源的分配和调度的基本单位,
进程是程序执行的基本实体
- 线程:是操作系统能够进行运算的最小单位,一个进程中可以并发执行多个线程,
每个线程并行执行不同的任务
浏览器的进程和线程
渲染进程负责加载解析和渲染页面内容,每个标签页通常运行在独立的渲染进程中,每个渲染进程都包含多个线程
- GUI线程:GUI主线程(也称为UI线程)是浏览器中负责处理用户界面操作和渲染的线程。它负责响应用户的交互操作、更新页面的显示等任务
- JS引擎线程:负责
解释
和执行
JS代码,将JS代码转换成可执行指令,并按照指令的顺序进行执行- 在浏览器中,JS引擎的线程是单线程,一次只能执行一个任务,在执行JS代码时,用到了异步编程模型,通过事件循环机制处理异步任务,这样可以使得JS可以处理耗时的操作而不阻塞用户界面的相应
- 事件监听线程:浏览器中负责监听和触发事件的线程。它是浏览器的一部分,用于处理用户输入、网络操作、定时器等事件的触发和相应。
- 定时器线程负责管理和触发定时器相关的任务,如setTimeout和setInterval
- 异步http请求线程:当发起AJAX请求时,浏览器会创建一个独立的线程来处理网络请求,该线程负责与服务器进行通信并接收响应
插件进程负责控制网络使用的所有插件
GPU进程负责整个浏览器界面的渲染
三:同步和异步
在JavaScript
中,所有的任务都可以分为
- 同步任务:同步任务会在GUI线程(主线程)和JS引擎线程中进行执行
- GUI线程:主要负责页面的渲染,所以不能被长时间的同步代码阻塞
- JS引擎线程:会阻碍其它任务的执行,包括GUI主线程的执行,如果JS引擎线程上的同步任务多的话,会导致页面的渲染和用户交互被暂时中断
- 异步任务:异步执行的任务,比如
ajax
网络请求,setTimeout
定时函数等- 定时器线程
- 异步http请求线程
- 事件监听线程
三:宏任务和微任务
异步任务又分为宏任务和微任务
- 宏任务:需要在事件循环中
单独执行
的任务单元- 渲染事件(如绘制页面、重新布局等)
- 用户交互事件(如鼠标点击、键盘事件等)
- 定时器事件(如
setTimeout
和setInterval
的回调函数) - 网络请求完成、文件读写完成等异步操作的回调函数
- 微任务:在当前宏任务执行完毕后立即执行的任务单元
Promise
的回调函数Object.Observer
MutaionObserver
的回调函数
四:事件循环机制
- 在事件循环中,调用栈和事件队列是两个重要的组成部分
- 调用栈:当执行到JS代码时候,函数调用会被添加到调用栈中,按照
先进后出
的顺序执行 - 任务队列:存储着
待执行
的异步任务,任务队列分为宏任务队列
和微任务队列
- 宏任务队列:存储着需要在事件循环中单独执行的任务,如定时器回调、事件回调等。
- 微任务队列:存储着需要在当前宏任务执行完毕后立即执行的任务,如 Promise 的回调函数、MutationObserver 的回调函数等
- 调用栈:当执行到JS代码时候,函数调用会被添加到调用栈中,按照
事件循环流程
- 当执行同步代码的时候,函数调用会依次进入到调用栈中执行
- 当遇到异步任务时,定时器回调,定时器到期时,回调函数会进入到宏任务队列中等待执行
- 任务队列中的任务会等到调用栈为空时候,事件循环会从任务队列取出一个任务加入到调用栈中执行
注意
- 微任务队列具有更高的优先级,会在下一个宏任务执行之前被处理。所以,当微任务队列不为空时,即使宏任务队列中有待执行的任务,也会先处理微任务队列中的任务。
代码
1 | console.log(1) |
- 先执行同步代码,将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