经典题:在浏览器里,从输入 URL 到页面展示,这中间发生了什么?
一.导航开始(点击ENTER 键后)
当输入一个完整的URL会被浏览器主进程,点击ENTER 键后,浏览器主进程会监听到输入的Url 通过IPC 协议通知到 网路进程,网络进程会根据当前的Url判断出两种情况
- 有缓存: 不请求网络请求,直接读取缓存数据返回浏览器进程 缓存又分一下情况
- 有缓存,且缓存没有过期
是否过期通常是通过目标地址 设置的 HTTP 响应/请求头 相关参数来判断是否过期 Cache-Control: max-age= 过期时间,在规定时间内都不用再请求服务器,直接读取本地数据,no-cache / must-revalidate: 协商缓存,根据服务器的返回结果,判断是否需要缓存
- 没有缓存: 进入请求流程,先通过DNS解析,获取服务器的ip,进行TCP 链接,并把需要的数据放进请求头,或者body内,拼接到get 请求的后面,然后等待目标服务器的返回,解析服务器返回的响应头,返回内容主要分下面几种情况
- 404||502 等错误码 代表 当前Url无法到达,无法看到预期页面
- 301||302 需要重新重定向,读取返回头的Location: 字段,重新请求其Value
- 200 等正确码,准备读取数据
2.准备(读取数据)
当服务器返回200是,网络进程就要准备读取数据,浏览器通过哪种方式读取数据,通过服务器返回的Content-Type 类型,
- application/octet-stream :字节流,一般直接进入浏览器的下载环节
- text/html:需要渲染数据,网路进程会通知到渲染进程进行渲染,
发送"提交文档"的message,渲染进程接收到信息后,建立通信管道,数据传输完成后,通知浏览器进程,浏览器进程会刷新 界面,开始渲染
3.开始渲染
> 现在web 页面的开发,主要用HTML,CSS,JavaScript,渲染引擎会把,这三种组合起来的渲染"渲染原数据",通过浏览器的解析成浏览器认识的结果文件,然后渲染成页面
整个渲染流水线过程:(Html,Css,Js)->渲染的各个子阶段-> PC 浏览器
子阶段包括:1. 构建DOM 2.解析CSS样式 3.布局阶段 4,分层 5.绘制 6. 分块
7.光栅化,8合成
先认识三剑客:HTML,CSS,JavaScript
- HTML: 超文本 标记语言 如
<p/>我们可以理解为裸体的人类本身 - CSS:叠层样式表 如:
{width: 12px}人的衣服 - JavaScript: 高级应用语言,
fun{}人的动作,如行走,吃饭等
##HTML(构建DOM)
DOM 是树状结构,主要是解析HTML 文件生成 DOM(创建人的骨骼胳膊和腿先创建出来), 如图
解析CSS 样式
浏览器接收到 CSS 文本后,会通过渲染引擎转换为浏览器器认识的styleSheets(穿衣服),并且具有DiY的能力查找,和修改能力,如下
.btn{
display:inline-flex;
align-items:center;
justify-content:center;
gap:0.5rem;
appearance:none;
border:0;
padding:0.5rem 1rem;
font-size:1rem;
line-height:1;
border-radius:4px;
cursor:pointer;
transition:background 160ms ease, transform 120ms ease;
}
图中出现了rem 这种是不被浏览器直接认识的,浏览器提过转换styleSheets 标准化的计算值 解析浏览器认识的px 像素值(1rem =16px)
前面我们说了Dom元素 是树形结构,树形结构Css 样式存在一定的继承关系,比如一些基础的样式是可被子类继承的,常见可继承关系:
color、font-family、font-size、font-style、font-weight、line-height、text-align、 text-indent、text-transform、visibility、direction、quotes 等。
.parent { color: #0b69ff; font-size: 18px; background: #fff; }
/* 自动继承(无需写) */
.parent p { /* 继承 color 和 font-size */ }
/* 覆盖继承值 */
.override { color: crimson; }
/* 强制从父元素继承(即使该属性通常被其他规则设置) */
.force { color: inherit; }
/* 取消继承/重置样式 */
.reset { all: unset; /* 或单独用 color: initial; */ }
但是如果子节点元素有设置样式就会使用就近原则的样式:其优先级为:(inline style > id > class > 元素选择器,等等)。
JavaScript
是让DOM 有行为能力和重新装扮能力,就像人的行为,跳舞重新穿衣服。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JavaScript 对 DOM 的影响演示</title>
<style>
.highlight { color: red; font-weight: bold; }
.new-element { background-color: lightblue; padding: 10px; margin: 5px; }
</style>
</head>
<body>
<h1>演示 JavaScript 对 DOM 的动态修改</h1>
<!-- 初始内容 -->
<div id="div1">初始文本:1</div>
<div id="div2">第二个 div:test</div>
<!-- 按钮触发 JS 修改 -->
<button id="changeTextBtn">修改第一个 div 文本</button>
<button id="changeStyleBtn">改变第二个 div 样式</button>
<button onclick="showToast()">显示 Toast</button>
<div id="toast" style="display:none; position:fixed; bottom:20px; right:20px;
background:#333; color:#fff; padding:10px; border-radius:5px;">
这是一句 Toast 消息!</div>
<script>
// 获取元素
const div1 = document.getElementById('div1');
const div2 = document.getElementById('div2');
// 修改样式
div2.style.color = 'green'; // 动态改变颜色
div2.style.fontSize = '20px'; // 改变字体大小
// 修改文本
document.getElementById('changeTextBtn').addEventListener('click', () => {
div1.innerText = 'time.geekbang'; // 动态改变文本内容
div1.classList.add('highlight'); // 添加样式类
});
// 吐司
document.getElementById('addElementBtn').addEventListener('click', () => {
const toast = document.getElementById('toast');
toast.style.display = 'block';
});
</script>
</body>
</html>
- 影响DOM构建(影响装扮)
浏览器在解析 DOM 树时,当遇到 <script> 标签时,会立即暂停 HTML 的解析过程,转而通知 JavaScript 引擎执行脚本。如上面代码- 直接修改第二个 <div> 的样式:div2.style.color = 'green'; div2.style.fontSize = '20px';,让文本瞬间变绿并放大。当解析脚本时页面渲染会暂时“停顿”,直到脚本运行完毕。这种机制确保了 JavaScript 能实时干预和修改 DOM 结构。
- 影响Dom 的行为
浏览器将 HTML 解析成的树状结构对象模型,放在内存中,div 节点是可被js 访问的,JavaScript 通过浏览器提供的 Web API(V8)获取节点的活引用,给元素节点添加监听,当点击节点触发监听,直接影响内存中Dom树,JS 修改 DOM 后,浏览器不会立即渲染,而是通过事件循环(Event Loop)触发“重排”(reflow,重新计算布局)更新视图。整个流程如下:
构建布局 Layout
- 在样式确定后,就要确定每个节点的位置,会根据DOM 树生成 布局树,浏览器会遍历所有的可见节点,并计算节点位置添加到布局树中。浏览器会把每个节点的css 设置的大小距离计算获取相对的信息重新放到布局树中如:
left:20px; right:20px; width:auto;
used_width=600−20−20=560px.
- 分层 图形树 除了定位外 还有一些动效,这些动效为特殊的节点,浏览器要为这些特殊的节点生成图层树,比如Z轴 层级,相对布局,UI 裁剪等,
4.渲染
生成显示资源位图
当完成布局定位后,获取每个元素需要绘制的内容,绘制会有个绘制列表,通过渲染引擎的合成线程执行绘制指令来完成的,上面我们已经确定了图形树,浏览器会根据图形和布局树和浏览器、设备内存、devicePixelRatio,生成瓦片,浏览器会根据我们可见的视图的优先级通过栅格化把每个瓦片生成位图,图块(tile)为栅格化的最小执行单元。渲染进程会将每个图块的栅格化任务分发到一个专用的栅格化线程池中并行处理,执行流程示意如下:
栅格化 是通过在专用的栅格(raster)线程池用 CPU 并行栅格化大多数瓦片,然后把位图上传到 GPU 进行合成。 生成位图是在渲染进程,ipc跨进程 合成在GPU进程,
显示
渲染进程在专用的栅格线程池中把页面拆成若干图块并栅格化为位图(tiles);合成线程(compositor)收集这些已栅格化的图块,生成绘制命令(DrawQuad),并通过进程间通信把这些命令及位图传给浏览器端的可视化组件(viz)或 GPU 进程。接收端把每个 DrawQuad 对应的位图上传为 GPU 纹理、在合成阶段按变换与透明度将各图层纹理拼合到帧缓冲,最后提交(swap buffers)把帧缓冲输出到屏幕。部分配置下(如启用 GPU rasterization 或不同引擎实现),栅格化可能在 GPU 进程/线程直接完成,减少位图传输。