普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月2日首页

【大白话前端 02】网页从解析到绘制的全流程

2026年3月2日 16:14

上一章我们讲到,浏览器在网络层面一路跋山涉水,最终把被切碎的数据包拼装成了一份纯文本文件。拿到文件后,便立刻交给了浏览器内部的渲染机器。

这台机器本质上就是一个精密的代码代工厂。纯文本的 HTML、CSS 和 JS 是无法直接变成眼前绚丽的页面,机器必须执行一套严密的 5 步流水线工序,把纯文本拼装成最终的交互画面。

graph TD
    A[HTML 代码] --> B(解析生成 DOM 树)
    C[CSS 代码] --> D(解析生成 CSSOM 树)
    B --> E{合成 Render Tree 渲染树}
    D --> E
    E --> F(Layout 布局计算)
    F --> G(Paint 绘制出图)

第一步:解析图纸,搭起车架子 (DOM 树)

浏览器无法直接阅读 HTML 里的尖括号文本。它做的第一件事,就是把这些嵌套的文本标签,转译成机器能懂的层级结构DOM 树。这棵树就是整个页面的车架子,规定了哪里是车门(<div>),哪里是方向盘(<button>)。

第二步:制备车漆配置单 (CSSOM 树)

骨架搭完后,浏览器紧接着解析 CSS 文本。不管样式写在哪,都会被统一合并梳理成另一棵树CSSOM。它记录了这扇门用什么颜色喷漆,那个轮毂多大尺寸。

致命阻塞:当流水线遇到 JavaScript

核心定律: 由于浏览器里的 JS 是单线程的,机器无法做到一边解析 HTML,一边去执行 JS。

所以一旦遇到了 <script> 标签,整条流水线会立刻强制停止。机器必须先去下载并执行完这段 JS 代码,然后才能继续解析HTML。这会导致网页首屏白屏卡住。

为了不让 JS 阻塞页面的渲染显示,我们有 3 种解法:

解法 1:传统写法 把所有 <script> 标签扔到 HTML 的最底部(也就是 </body> 前面)。这样机器会一口气先把页面画完,最后再去下 JS。

解法 2 和 解法 3:让机器一心二用 如果非把 JS 写在头部(<head>),可以给它加上 asyncdefer 属性。它们都能让机器开启后台并行下载,但之后的行为完全不同:

写法属性 下载时机 (谁在下?) 执行时机 (何时跑?) 适用场景 结论
啥都不加 阻塞流水线,必须下完 马上执行,流水线继续停工 旧时代的默认写法 极慢,已被淘汰
加 async 在后台并行下载 下载完马上霸道插队执行,流水线被迫停工 互相独立、不需要按顺序跑的脚本(如:百度统计代码) 谁先下完谁执行,适合独立模块
加 defer 在后台并行下载 必须等所有 HTML 全解析完,再按顺序排队执行 需要操作 DOM、或有关联顺序的核心业务代码 无脑首选,最稳健防白屏

正确做法: 绝大多数情况下,直接无脑写 <script src='xxx.js' defer></script>。这相当于告诉机器:你先去后台排队下吧,继续画你的 HTML 图纸,等你看完全部图纸了最后再来行这些 JS。

第三步:合成渲染树 (组装核心部件)

DOM 树只讲结构,CSSOM 树只讲样式。到了第三步,流水线把这两份图纸合并,生成真正用于显示的渲染树 (Render Tree)。这就好比按图纸把车架子和车漆拼成了待喷漆的白模车。

常见错误: 渲染树里只保留需要显示的节点。如果一个元素加了 display: none,或者它本身就是 <head> 这种不可见标签,这一步会被直接丢弃,根本不进后续流程。

第四步:布局 Layout (计算尺寸与位置)

进入布局阶段,也叫重排 (Reflow)。在这个阶段,机器精确计算每个零件在屏幕上的确切坐标(距离顶部、左边多少)和尺寸(宽、高)。相当于实地测绘占地面积。

第五步:绘制 Paint (上色与纹理)

最终坐标算清后,最后一步就是调用绘图 API,把真实像素画在屏幕上。这步也叫重绘 (Repaint)。至此,你在屏幕上终于看到了网页。

重排与重绘的性能损失

理解了流水线,你在写代码时就要尽量规避让机器大范围返工的操作。

概念 做了什么骚操作 机器被你逼着怎么返工? 性能开销
重排 (Reflow) 动了位置(如修改 width、margin) 布局 + 绘制这两步推翻重走,重新测绘周边所有元素位置。 极大(极易卡顿掉帧)
重绘 (Repaint) 只是换色(如修改 color、background) 尺寸和占地没变,只需把绘制这一步重走一遍。 较小

避坑指南:做“动画/动态位移”时,用 transform 代替 margin

1. 底层逻辑(为什么加了 transform 动画就不卡了?):

  • 如果你用 margin 做动画: 等于你告诉主流水线:“我要把这个小螺丝往右挪 100 像素”。流水线的刻板反应是:“收到,但螺丝位置变了,旁边的挡板可能要跟着动,车架子的重心可能变了——不行,我得把周边所有相连元素的受力面积和坐标全重新计算一遍(触发大规模重排),然后把它们全员重新上色(触发重绘)。” 这就叫牵一发而动全身。
  • 如果你用 transform 做动画: 浏览器一旦看到 transform 这个词,立刻懂了。它会在之前第 5 步(绘制 Paint)结束后,把这个加了位移的元素单独剪下来,变成一张独立的透明贴纸(前端术语叫:提升为独立图层 Layer)。 发生位移动画时,不仅不动兄弟元素的布局,它自己连颜色都不用重涂。这张画好的贴图直接过继给显卡(GPU),显卡不需要算尺寸,只需要像推玻璃一样,把这张完整的贴纸“滑”到新位置。这最后凌驾于流水线之上滑动图层拼装的工序,叫作合成(Composite)。

2. 终极一问:能无脑全用 transform 替代 margin 布局吗? 绝对不行! 正因为 transform 变成了悬浮的贴纸,它在视觉上飞走后,原地的物理坑位还在,它根本碰不到、也挤不开周围的兄弟元素

  • 结论:搭静态页面基础架构(抢地盘)时,就得砸重排的开销,老老实实用 margin / padding。而在做轮播图、弹窗飞入、元素悬浮变大等频繁动来动去的交互动画时,死磕 transform

了解了浏览器的渲染流水线,下一个要面对的现实是:不同浏览器(如 Chrome、Safari)的流水线标准并不完全一样。如果按个人直觉随意写代码,很容易出现不同设备上样式错位,或是触发刚才讲过的性能卡顿。 为此,整个前端行业制定了一套统一的“图纸底线规范”。

下一章请看:【大白话前端 03】Web 标准与最佳实践,我们将直接拆解这套规范,看看实战中到底该用什么样的标准来编写代码。

昨天以前首页

JavaScript 基础入门

2026年2月26日 17:19

如果把网页比作一个人:

  • HTML 是骨架:决定哪里是头、哪里是手。
  • CSS 是皮肤和衣服:决定长得好不好看。
  • JavaScript(简称 JS)是肌肉和神经:决定网页能不能“动”起来。

没有 JS,网页就是一张静态海报;有了 JS,网页就变成了可以互动的游戏、能验证密码的表单、能滑动加载的瀑布流。

趣味科普:1995 年,网景公司的程序员仅用 10天 就写出了 JS 的初版。为了蹭当时 Java 语言的热度,起名叫 JavaScript,但实际上它俩毫无关系(就像“雷锋”和“雷峰塔”)。如今,JS 已经成为能写网页、做手机 APP、写后端服务的“全栈霸主”。


核心前置知识:小白必懂的 3 个概念

在动手写代码前,必须先搞懂这 3 个“绕不开”的基础概念。

1. 变量:装数据的“带标签盒子”

变量的作用是给数据起个名字,方便反复使用。

声明关键字 特点 适用场景 示例
const 不可变(常量) 声明后不会再改变的数据 const name = "张三";
let 可变(变量) 声明后可能会被修改的数据 let age = 20; age = 21;

2. DOM:网页的“结构图纸”

DOM(文档对象模型) 是浏览器把 HTML 转换成的一棵“对象树”。 JS 看不懂 HTML 代码,但它能看懂 DOM 树。JS 拿着这张图纸,就能精准找到网页上的任何元素并修改它。

graph TD
    HTML((html)) --> HEAD((head))
    HTML --> BODY((body))
    HEAD --> TITLE[title: 网页标题]
    BODY --> H1[h1: 标题文字]
    BODY --> IMG[img: 图片]
    BODY --> BUTTON[button: 按钮]
    
    style HTML fill:#f9f2f4,stroke:#d04437,stroke-width:2px
    style BODY fill:#e6f2ff,stroke:#007bff,stroke-width:2px
    style H1 fill:#d9ead3,stroke:#93c47d
    style IMG fill:#d9ead3,stroke:#93c47d

3. 事件监听:给网页安上“传感器”

让 JS 盯着用户的动作(点击、滑动、输入),一旦触发,就执行对应的代码。

  • 核心语法addEventListener("事件类型", 触发后执行的代码)

综合实战:打造你的第一个交互网页

我们将通过一个综合案例,一次性掌握 改文字、换图片、存数据 三大核心技能。

步骤 1:搭建 HTML 骨架

新建 index.html,注意 <script> 标签里的 defer 属性,这是新手避坑的关键!

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>我的交互网页</title>
  <!-- 引入 JS。defer 表示“后台加载,等 HTML 渲染完再执行”,防止 JS 找不到元素 -->
  <script defer src="main.js"></script>
</head>
<body>
  <h1>原来的标题</h1>
  <img src="images/firefox-icon.png" alt="火狐图标" id="myImg">
  <button id="changeUserBtn">切换用户</button>
</body>
</html>

步骤 2:编写 JS 代码

新建 main.js,把下面的代码复制进去。我们分三步来实现交互:

魔法 1:自动修改标题(DOM 操作)

// 1. 找元素:用 querySelector 找到 <h1>
const myHeading = document.querySelector("h1");

// 2. 改内容:修改它的文字属性
myHeading.textContent = "Hello World!";

魔法 2:点击切换图片(事件监听 + 属性修改)

// 1. 找元素:找到图片
const myImage = document.querySelector("#myImg");

// 2. 听事件:监听点击动作
myImage.addEventListener("click", () => {
  // 3. 做处理:获取当前图片路径
  const currentSrc = myImage.getAttribute("src");
  
  // 4. 更页面:判断并切换路径
  if (currentSrc === "images/firefox-icon.png") {
    myImage.setAttribute("src", "images/firefox2.png"); // 换新图
  } else {
    myImage.setAttribute("src", "images/firefox-icon.png"); // 换回原图
  }
});

魔法 3:记住用户的名字(本地存储 + 弹窗)

这里我们会用到浏览器的“小仓库”——localStorage。它能把数据存在用户电脑上,刷新网页也不会丢。

const myButton = document.querySelector("#changeUserBtn");

// 定义一个设置名字的函数
function setUserName() {
  // prompt 会弹出一个输入框
  const userName = prompt("请输入你的名字:");
  
  if (!userName) { 
    setUserName(); // 如果没输入,就重新弹窗要求输入
  } else {
    // 存数据:把名字存进本地仓库,贴上标签叫 "savedName"
    localStorage.setItem("savedName", userName);
    // 模板字符串:用反引号 ` 和 ${} 把变量塞进句子里
    myHeading.textContent = `JS 太酷了,${userName}!`;
  }
}

// 页面刚打开时的判断逻辑
if (!localStorage.getItem("savedName")) {
  setUserName(); // 没存过,弹窗让用户输入
} else {
  const savedName = localStorage.getItem("savedName"); // 存过,直接取出来
  myHeading.textContent = `JS 太酷了,${savedName}!`;
}

// 点击按钮时,重新设置名字
myButton.addEventListener("click", () => {
  setUserName();
});

总结:JS 交互的“万能 4 步曲”

不管是做简单的按钮点击,还是复杂的网页游戏,JS 的核心交互逻辑永远逃不开这 4 步。把这张图印在脑子里,你就算真正入门了!

graph TD
    A[1. 找元素<br>querySelector] -->|找到目标| B[2. 听事件<br>addEventListener]
    B -->|用户触发| C[3. 做处理<br>逻辑判断/存取数据]
    C -->|计算完毕| D[4. 更页面<br>修改 DOM/样式]
    
    style A fill:#ffe6cc,stroke:#ff9900
    style B fill:#e6f2ff,stroke:#007bff
    style C fill:#e6ffe6,stroke:#28a745
    style D fill:#f9f2f4,stroke:#d04437

新手避坑指南

  1. 报错 Cannot read properties of null(找不到元素)
    • 原因:JS 执行时,HTML 还没加载完。
    • 解决:检查 <script> 标签有没有加 defer 属性,或者把 <script> 放到 </body> 标签的前面。
  2. 名字存了但刷新后不显示
    • 原因:存数据(setItem)和取数据(getItem)时,用的“标签名(key)”拼写不一致。
    • 解决:仔细检查字符串,比如是不是把 savedName 错拼成了 saveName
❌
❌