在现代 Web 开发中,了解浏览器如何渲染页面以及 JavaScript 如何影响页面加载流程可以更好地理解前端开发的核心原理。
一、浏览器渲染进程概述
浏览器的渲染进程(Render 进程)主要负责页面的渲染、脚本执行和事件处理等工作。为了避免因单个页面崩溃而导致整个浏览器崩溃,每个页面都有独立的渲染进程。
Render 进程是一个多线程架构,包含以下主要线程:
-
GUI 渲染线程
-
负责渲染浏览器界面,包括解析 HTML 和 CSS,构建 DOM 树和 RenderObject 树,以及执行布局和绘制等操作。
-
当界面需要重绘(Repaint)或由于操作引发回流(Reflow)时,该线程会被激活。
-
注意:GUI 渲染线程与 JavaScript 引擎线程是互斥的。当 JavaScript 引擎执行时,GUI 线程会被挂起,所有需要更新的 GUI 操作会被暂存到一个队列中,只有当 JavaScript 引擎空闲时,这些操作才会被执行。
-
-
JS 引擎线程
-
也称为 JavaScript 内核,如 V8 引擎,负责处理 JavaScript 脚本程序。
-
单线程特性 :JavaScript 主线程一次只能执行一个任务(同步代码),同步任务按顺序依次执行,不会阻塞其他 JavaScript 代码的执行。
-
任务队列(Task Queue) :异步任务的回调函数会被放置在这个队列中。有两种类型的任务队列:
-
宏任务(macrotask)队列 :例如
setTimeout
、setInterval
的回调,网络请求的回调,Document Object
的事件等。 -
微任务(microtask)队列 :由
Promise
、MutationObserver
、process.nextTick(Node.js 环境)
等生成的回调。
-
-
事件循环(Event Loop)机制
-
执行规则 :事件循环不断检测主线程是否空闲,如果空闲就从任务队列中取出任务执行。
-
工作过程 :
-
浏览器环境或 Node.js 环境中,JavaScript 引擎(如 V8)执行同步代码,完成主线程上的所有同步任务。
-
如果遇到异步任务(如
setTimeout
或setInterval
的回调),它们会被暂时搁置在相应的任务队列中。 -
引擎会监听宏任务和微任务队列中的任务:
-
微任务队列 的优先级高于 宏任务队列。在每次执行完主线程的一个同步任务之后,会先检查微任务队列,按顺序执行所有微任务,直到微任务队列空。
-
然后引擎会检查宏任务队列,取出一个任务执行。
-
-
-
-
-
事件触发线程
-
归属于浏览器而非 JavaScript 引擎,用于控制事件循环。
-
当执行诸如
Promise
等操作时,相关任务会被添加到事件线程中。当事件触发条件满足时,线程会将事件添加到待处理队列的队尾,等待 JavaScript 引擎的处理。
-
-
定时触发器线程
-
专门用于处理
setTimeout
和setInterval
等定时任务。 -
浏览器的定时计数器不依赖 JavaScript 引擎计数(因为 JavaScript 引擎是单线程的,若处于阻塞状态会影响计时的准确性)。当定时触发器线程计时完成后,会通知事件触发线程,将定时任务的回调函数添加到事件队列的队尾。
-
后台定时触发器线程计时的准确性问题:
-
当浏览器处于后台时,为了节省系统资源和电量消耗,浏览器可能会对定时触发器线程的执行频率进行优化调整。将
setTimeout
和setInterval
的执行间隔延长,导致原本应该按照设定时间间隔执行的任务被延迟执行,从而出现计时不准确现象,如果有需要定时触发器线程要必须在后台执行强需求的话可以使用。- 开启JS多线程web weoker,倒计时写在weborker里时,页面的tab不会影响到倒计时的计算
let webWorkDate = 100, date = 100; // 开启线程 const work = new Worker('worker.js'); setInterval(() => { date--; console.log('普通倒计数:', date); }, 1000); // 传输数据 work.postMessage({ time: webWorkDate }); console.log(work); // 监听线程 work.onmessage = (event) => { console.log(); console.log('Worker倒计数:', event.data.num); if (event.data.num === 0) { work.terminate(); //关闭线程 } }; //worker.js self.addEventListener( 'message', function (e) { setInterval(() => { let num = e.data.time--; self.postMessage({ num }); }, 1000); }, false );
-
-
-
异步 HTTP 请求线程
-
在使用
XMLHttpRequest
或fetch
等进行网络请求时,浏览器会开启一个新的线程负责请求。 -
当检测到状态变更时,若设置有回调函数,异步线程会生成状态变更事件,并将回调函数放入事件队列中,等待 JavaScript 引擎执行。
-
二、页面加载的整体执行步骤
-
加载整体 HTML 文件
- 浏览器首先会加载整个 HTML 文件,解析其结构。
-
解析 HTML 并建立 DOM 树
-
浏览器会从上到下解析 HTML 文件,构建 DOM 树。在解析过程中,遇到诸如
<script>
、<link>
等标签时,会下载和解析相应的内容。 -
如果是
<link>
标签,浏览器会解析 CSS 文件并构建 CSS 对象模型(CSSOM 树)。
-
-
结合 DOM 和 CSSOM 树生成 Render 树
- Render 树是 DOM 树和 CSSOM 树的结合体,用于描述页面中可见元素的布局和样式信息。
-
布局 Render 树(Layout/Reflow)
- 负责计算各元素的尺寸和位置等布局信息。
-
绘制 Render 树(Paint)
- 根据 Render 树中的信息,绘制页面的像素内容。
-
GPU 合成
- 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成,并显示在屏幕上。
三、HTML、CSS 和 JavaScript 的解析与执行
-
HTML 解析
-
浏览器从上到下解析 HTML 文件,构建 DOM 树。
-
遇到
<script>
标签时,若脚本是内部脚本,浏览器会立即解析并执行;若是外部脚本,浏览器会暂停解析 HTML,等待脚本下载完成后执行。
-
-
CSS 解析
- CSS 有三种声明方式:外联样式表、内联样式表和内部样式表。浏览器会根据这些样式构建 CSSOM 树,用于渲染页面的样式。
-
JavaScript 解析
- JavaScript 引擎负责解析和执行 JavaScript 脚本。执行 JavaScript 脚本时会阻塞 HTML 解析,因此建议将
<script>
标签放置在页面底部,以减少对页面加载的影响。
- JavaScript 引擎负责解析和执行 JavaScript 脚本。执行 JavaScript 脚本时会阻塞 HTML 解析,因此建议将
四、DOM 文档加载步骤
-
解析 HTML 结构:浏览器解析 HTML 文件,构建 DOM 树。
-
加载外部脚本和样式文件:加载外部的 JavaScript 和 CSS 文件。
-
解析并执行脚本代码:解析和执行 JavaScript 脚本。
-
执行事件绑定代码:如
$(function(){})
中的代码。 -
加载二进制资源:加载图片等二进制资源。
-
页面加载完毕:执行
window.onload
事件。
希望本文能帮助你更深入地理解浏览器的工作原理,关于如何优化页面加载性能咱们下回接着再聊。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)