一、浏览器的进程模型
1、何为进程?
进程是计算机中运行的程序实例。它是操作系统进行资源分配和调度的基本单位。每个进程都有自己的地址空间、内存和执行状态。进程之间相互独立,彼此不能直接访问对方的资源。进程间的通信需要通过特定的机制,例如管道、套接字等,需要双方同意才可交互。
2、何为线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以拥有多个线程,这些线程共享进程的资源,如内存空间、文件句柄等。
与进程相比,线程更加轻量级,因为线程在同一进程下共享内存空间和其他资源,而进程之间的资源一般是独立的。由于线程之间的切换开销较小,所以线程可以更高效地实现并发操作。
线程通常包括以下几种类型:
-
用户线程:由应用程序开发者创建和管理的线程,运行在用户空间。
-
内核线程:由操作系统内核创建和管理的线程,运行在内核空间。内核线程可以访问操作系统的所有资源,并且具有更高的优先级。
线程可以协同工作,共同完成复杂的任务,也可以提高程序的响应速度和并发处理能力。在多核处理器系统中,不同的线程可以被分配到不同的处理器核心上执行,从而实现并行处理。然而,线程之间的共享资源也可能导致竞争条件和死锁等问题,需要谨慎设计和管理。
3、浏览器的有哪些进程
a、主页进程类型
-
主进程(Browser Process):也称为浏览器引擎进程或浏览器内核进程,负责协调和控制整个浏览器的运行。它管理其他进程,处理用户界面的显示和用户交互,以及网络请求的处理。
-
渲染进程(Renderer Process):每个标签页(Tab)通常对应一个独立的渲染进程。渲染进程负责解析、布局和绘制网页内容,并执行相关的JavaScript代码。这种进程隔离的设计可以提高安全性和稳定性,因为即使一个标签页崩溃,其他标签页仍然可以正常运行(核心重点)。
-
插件进程(Plugin Process):用于运行浏览器插件,如Flash播放器等。为了增强浏览器的安全性和稳定性,现代浏览器通常将插件进程单独隔离,以防止插件的错误导致整个浏览器崩溃。
-
GPU进程(GPU Process):负责处理图形相关的任务,例如页面的绘制和动画效果。将图形任务交给独立的GPU进程可以提高性能和稳定性。
-
网络进程(Network Process):也称为网络代理进程,负责处理网络请求和数据传输。网络进程独立于渲染进程,可以提高安全性,并且可以并行处理多个网络请求,提高网页加载速度。
b、图解
打开浏览器任务管理器查看浏览器进程
设置及其他(下图的...) -> 更多工具->浏览器任务管理器
4、渲染进程有哪些线程、渲染主线程是如何工作的?
A、渲染进程中通常包含多个线程,每个线程负责不同的任务,常见的线程包括:
-
主线程(Main Thread):主线程负责处理用户交互、解析 HTML、构建 DOM 树、CSSOM 树、渲染树的构建和布局、页面绘制等操作。
-
GUI 渲染线程(GUI Rendering Thread):负责将渲染树中的元素绘制到页面上,包括文字、颜色、图像等的渲染。
-
事件触发线程(Event Thread):负责管理用户输入事件(如鼠标点击、键盘输入等)的响应。
-
定时器触发线程(Timer Thread):负责管理定时器,定时触发执行定时任务。
-
网络请求线程(Network Thread):负责处理网络请求、数据传输等网络相关操作。
B、渲染主线程是如何工作的?
a、主要任务
渲染主线程是浏览器中最繁忙的线程,需要它处理的任务包括但不限于:
-
HTML 解析和构建 DOM 树:渲染主线程会解析 HTML 文档,并将其转换成 DOM(文档对象模型)树的结构,表示网页的结构和内容。
-
CSS 解析和构建 CSSOM 树:渲染主线程会解析 CSS 样式文件,并将其转换成 CSSOM(CSS 对象模型)树的结构,表示网页的样式信息。
-
渲染树构建:渲染主线程会将 DOM 树和 CSSOM 树合并,形成渲染树(Render Tree),它描述了网页的视觉呈现结构。
-
布局(Layout):基于渲染树,渲染主线程会计算每个元素在屏幕上的准确位置和大小,以确定网页的布局。
-
绘制(Painting):渲染主线程会根据布局计算结果,将渲染树中的元素转换成绘制指令,然后交给浏览器的绘图引擎进行绘制,将内容显示在屏幕上。
-
响应用户交互:渲染主线程还负责处理用户的交互事件,例如鼠标点击、滚动等,根据用户的操作更新渲染树,并触发布局和绘制。
-
JavaScript 执行:渲染主线程还负责执行 JavaScript 代码,包括处理页面上的脚本、响应事件、更新页面状态。
b、如何调度任务(事件循环)
核心: 将任务排队
第一步.:在最开始的时候,渲染主线程会进入无限循环。
第二步:每⼀次循环会检查消息队列中是否有任务存在。如果有,就取出第⼀个任务执行,执行完⼀个后进入下⼀次循环;如果没有,则进入休眠状态。
第三步: 其他所有线程(包括其他进程的线程)可以随时向消息队列添加任务。新任务会加到消息队列的末尾。在添加新任务时,如果主线程是休眠状态,则会将其唤醒以继续循环拿取任务。
这样任务就可以有序执行了、这个排队执行任务的过程就叫事件循环
二、关于异步
1、如何理解异步
JavaScript 是一门单线程的语言,这意味着 JavaScript 代码在执行时只能使用一个主线程。在浏览器中,JavaScript 代码通常运行在浏览器渲染主线程中,这个主线程负责处理页面渲染、用户交互和 JavaScript 执行等任务。
由于渲染主线程是单线程的,如果 JavaScript 代码采用同步的方式执行,即按照代码的顺序依次执行,会导致浏览器渲染主线程在执行 JavaScript 代码期间无法处理其他任务,从而造成页面卡顿、无法及时响应用户操作等问题。这种情况被称为“阻塞”,因为主线程被某个任务阻塞而无法继续执行其他任务(这是绝对不行的)。
为了避免主线程阻塞,提高页面的响应速度和流畅性,JavaScript 在浏览器中通常采用异步执行的方式。通过事件循环机制和回调函数,JavaScript 可以将耗时的任务交给其他线程(如网络请求线程、定时器线程等)处理,并在任务完成后通过回调函数返回结果,从而避免主线程的阻塞。这样可以保证页面的渲染和用户交互不受影响,提高了用户体验。
未使用异步会形成"堵塞"
使用异步能解决"堵塞"
2、异步任务有哪些
异步任务包括以下几种常见类型:
-
定时器任务(Timer Tasks):使用
setTimeout
或setInterval
函数创建的定时器任务,可以在指定的时间间隔后执行回调函数。 -
事件监听任务(Event Listener Tasks):通过事件监听函数来处理用户交互、网络请求、文件加载等事件,当事件触发时执行相应的回调函数。
-
异步网络请求任务(Ajax/XHR Tasks):使用 XMLHttpRequest 或 Fetch API 发起的网络请求,在请求完成后执行相应的回调函数。
-
Promise 回调任务(Promise Jobs):通过 Promise 对象的
then
、catch
、finally
方法注册的回调函数,在 Promise 状态改变时执行。 -
微任务(Microtasks):通过
Promise
的then
、catch
、finally
方法、MutationObserver
、queueMicrotask
函数等注册的微任务,在主线程任务执行完毕、渲染之前执行。
3、异步任务执行的优先级
过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是⼀种更加灵活多变的处理方式。
W3C最新解释网址:https://html.spec.whatwg.org/multipage/webappapis.html#p erform-a-microtask-checkpoint
任务没有优先级,在消息队列中先进先出、但消息队列是有优先级的
根据 W3C 的最新解释: 每个任务都有⼀个任务类型,同⼀个类型的任务必须在⼀个队列,不同类型的任务可以分属于不同的队列。 在⼀次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。 浏览器必须准备好⼀个微队列,微队列中的任务优先所有其他任务执行。
-
延时队列(Timer Queue):用于存放通过
setTimeout
和setInterval
创建的定时器到达后的回调任务。当定时器到达指定时间时,相应的回调任务将被添加到延时队列中等待执行。这些任务的优先级一般为「中」,即介于交互队列和微队列之间。 -
交互队列(Interaction Queue):用于存放用户操作后产生的事件处理任务,例如点击事件、键盘事件、鼠标事件等。这些任务对应的回调函数会被添加到交互队列中,其优先级通常为「高」,因为用户交互往往需要得到及时响应。
-
微队列(Micro Queue):用于存放需要尽快执行的任务,其优先级通常为「最高」。微队列中的任务包括 Promise 的回调函数、MutationObserver 的回调函数等,它们会在当前任务执行完毕后立即执行,以确保及时更新页面状态或响应异步操作的结果。
总结: 同步任务> 微队列>交互队列>延时队列
4、代码演示
console.log('1'); // 同步任务
setTimeout(function() {
console.log('2'); // 延时队列
}, 2000);
Promise.resolve().then(function() {
console.log('3'); // 微队列
Promise.resolve().then(function() {
console.log('4'); // 微队列
Promise.resolve().then(function() {
console.log('5'); // 微队列
setTimeout(function() {
console.log('6'); // 延时队列
}, 2000);
});
Promise.resolve().then(function() {
console.log('7'); // 微队列
});
setTimeout(function() {
console.log('8'); // 延时队列
}, 0);
console.log('9'); // 同步任务
输出结果为
1
3
4
5
7
9
8
2
6
五、总结什么是事件循环
事件循环⼜叫做消息循环,是浏览器渲染主线程的工作方式。 在 Chrome 的源码中,它开启⼀个不会结束的 for 循环,每次循环从消息 队列中取出第⼀个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾即可。 过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是⼀种更加灵活多变的处理方式。 根据 W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在同⼀ 个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级, 在⼀次事件循环中,由浏览器自学决定取哪⼀个队列的任务。但浏览器必须 有⼀个微队列,微队列的任务⼀定具有最高的优先级,必须优先调度执行。