在前端开发中,事件的执行顺序是挺重要的,要了解事件循环,需要先了解线程和进程的概念。
进程和线程
什么是进程?
进程是一个正在执行的程序实例,它拥有独立的内存空间。在操作系统中,每个程序都有自己的进程,每个进程之间互相独立,彼此之间的通信需要经过特殊的协商和管理。
什么是线程?
线程是进程中的执行单元,一个进程至少包含一个线程,这个线程被称为“主线程”。当程序需要并发执行多个任务时,主线程会创建更多的线程来处理其他任务。因此,进程可以包含多个线程,多个线程可以共享进程的资源。
浏览器中的进程和线程
浏览器是一个多进程、多线程的应用程序。为了提高性能和稳定性,浏览器通常会启动多个进程来分担不同的任务。常见的浏览器进程包括:
- 浏览器进程:负责界面渲染、用户交互以及管理其他进程。
- 网络进程:负责网络资源的加载和处理。
- 渲染进程:负责页面渲染,它会创建一个渲染主线程来执行页面的 HTML、CSS 和 JavaScript 代码。
通常,每个标签页都会启动一个独立的渲染进程,以确保不同标签页之间互不干扰。
渲染主线程的任务
- 解析 HTML、CSS 和 JavaScript
- 执行样式计算和布局
- 绘制页面元素
- 处理用户事件(如点击、输入)
- 执行定时器回调函数等
异步渲染
浏览器通过异步执行来避免主线程阻塞。对于一些需要等待的任务,如网络请求、定时器等,浏览器不会让主线程等待它们完成,而是将这些任务交给其他线程处理。当任务完成时,回调函数会被放入消息队列等待执行。这样,主线程可以继续执行其他任务,保持响应速度。
引入:如何处理多任务
渲染进程中的主线程需要处理许多不同的任务,这就带来了一个问题:如何合理安排任务的执行顺序?例如:
- 当正在执行一个 JavaScript 函数时,如果用户点击了按钮,是否应该立即响应点击事件?
- 当一个计时器的时间到期时,是否应立刻执行回调函数?
为了解决这个问题,浏览器使用了消息队列和事件循环机制。渲染主线程通过以下方式管理任务:
- 渲染主线程处于一个无限循环中,不断检查消息队列。
- 每次循环时,如果消息队列中有任务,主线程会取出并执行它们。如果没有任务,则主线程会休眠。
- 当其他线程向队列添加任务时,主线程会被唤醒,继续执行任务。
这种方式保证了浏览器的主线程始终保持活跃,并且每个任务都能按顺序得到执行。
任务的优先级顺序
任务队列按照优先级分为不同的队列,其中微任务队列的优先级最高。现代浏览器会先执行微任务队列中的任务,再执行宏任务队列中的任务。例如,Promise
和 MutationObserver
的回调会被放入微任务队列,而 setTimeout
和事件处理回调则会被放入宏任务队列。
补充:JS定时器的进度
虽然浏览器提供了 setTimeout
和 setInterval
来进行定时任务调度,但这些定时器的精确度并不高。
- 计算机硬件的时钟精度有限。
- 操作系统对时间的管理会有微小的误差。
- 浏览器事件循环和任务调度的机制也会影响定时器的执行时间。
因此,定时器的执行时间可能会有一定的偏差,尤其是在嵌套定时器或任务较多时,延迟可能更明显。