【浏览器】页面加载原理详解

目录

  1. 概述
  2. 浏览器架构基础
  3. 页面加载完整流程
  4. HTML解析与DOM构建
  5. CSS解析与样式计算
  6. JavaScript执行机制
  7. 渲染树构建与布局
  8. 绘制与合成
  9. 性能优化实践
  10. HTTP/3与QUIC协议详解
  11. [Service Worker详解](#Service Worker详解)
  12. 浏览器安全机制
  13. 浏览器缓存机制详解
  14. JavaScript内存管理
  15. 首屏渲染指标详解
  16. 浏览器调试技巧
  17. 移动端浏览器特殊考虑
  18. 总结

概述

当用户在浏览器地址栏输入URL并按下回车键后,一个看似简单的操作背后,实际上发生了极其复杂的处理过程。从网络请求到最终页面渲染,浏览器需要协调多个模块协同工作。理解这个过程对于前端开发者至关重要,它不仅能帮助我们编写更高效的代码,还能在性能优化时做出正确的决策。


浏览器架构基础

现代浏览器采用多进程架构,主要包括以下进程:

主要进程

  1. 浏览器主进程(Browser Process)

    • 负责浏览器界面显示、用户交互、子进程管理
    • 网络资源管理、文件访问等
  2. 渲染进程(Renderer Process)

    • 负责页面渲染、JavaScript执行
    • 每个标签页通常对应一个渲染进程(同源策略下可能共享)
  3. GPU进程(GPU Process)

    • 负责GPU加速的渲染任务
    • 3D CSS、WebGL等图形处理
  4. 网络进程(Network Process)

    • 负责网络资源加载
    • DNS解析、TCP连接、HTTP请求等
  5. 插件进程(Plugin Process)

    • 负责浏览器插件运行

渲染进程内部架构

渲染进程内部采用多线程架构:

  • 主线程(Main Thread):DOM解析、CSS解析、JavaScript执行、布局、绘制
  • 合成线程(Compositor Thread):图层合成、滚动优化
  • 光栅化线程(Raster Thread):将图层转换为位图
  • Worker线程:Web Worker、Service Worker等

页面加载完整流程

整体流程图

HTML CSS JS 用户输入URL DNS解析 建立TCP连接 发送HTTP请求 接收响应 响应类型 HTML解析 CSS解析 JS解析执行 构建DOM树 构建CSSOM树 执行JS 合并DOM和CSSOM 构建渲染树RenderTree 布局计算Layout 绘制Paint 合成Composite 显示页面

详细阶段说明

阶段1:导航阶段(Navigation)

1.1 DNS解析(Domain Name Resolution)

当浏览器接收到URL后,首先需要将域名转换为IP地址:

复制代码
用户输入: https://www.example.com/index.html
         ↓
DNS查询: www.example.com → 192.0.2.1

DNS解析过程:

  • 检查浏览器DNS缓存
  • 检查操作系统DNS缓存
  • 检查路由器DNS缓存
  • 向本地DNS服务器查询
  • 递归查询根域名服务器、顶级域名服务器、权威域名服务器

优化策略:

  • DNS预解析:<link rel="dns-prefetch" href="//cdn.example.com">
  • 预连接:<link rel="preconnect" href="https://cdn.example.com">(建立DNS、TCP、TLS连接)

1.2 TCP连接建立

TCP三次握手过程:

复制代码
客户端 → SYN → 服务器
客户端 ← SYN-ACK ← 服务器
客户端 → ACK → 服务器

HTTPS额外步骤:

  • TLS握手(TLS 1.2/1.3)
  • 证书验证
  • 密钥交换
  • 建立加密通道

1.3 HTTP请求发送

浏览器构建HTTP请求:

http 复制代码
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0...
Accept: text/html,application/xhtml+xml
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive

HTTP/2特性:

  • 多路复用(Multiplexing)
  • 头部压缩(HPACK)
  • 服务器推送(Server Push)
  • 二进制分帧

1.4 服务器响应

服务器返回HTTP响应:

http 复制代码
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 12345
Cache-Control: public, max-age=3600
ETag: "abc123"

关键响应头:

  • Content-Type: 资源类型,影响解析方式
  • Content-Encoding: 压缩方式(gzip, br等)
  • Cache-Control: 缓存策略
  • Transfer-Encoding: chunked: 分块传输

阶段2:解析阶段(Parsing)

HTML解析与DOM构建

2.1 HTML解析器工作流程

HTML解析是一个增量解析过程,采用流式解析(Streaming Parsing):
网络进程 HTML解析器 DOM构建器 JavaScript引擎 接收HTML字节流 字节流解码(UTF-8) 词法分析(Tokenization) 生成Token 构建DOM节点 插入DOM树 遇到<script>标签 暂停解析,执行JS JS执行完成,恢复解析 网络进程 HTML解析器 DOM构建器 JavaScript引擎

HTML解析详细步骤:

步骤1:字节流解码

复制代码
原始字节: 3C 68 74 6D 6C 3E
         ↓ UTF-8解码
文本内容: <html>

步骤2:词法分析(Tokenization)

HTML解析器使用状态机进行词法分析:

javascript 复制代码
// 简化版状态机示例
const states = {
  DATA: 'DATA',           // 数据状态
  TAG_OPEN: 'TAG_OPEN',   // 标签开始
  TAG_NAME: 'TAG_NAME',   // 标签名
  BEFORE_ATTRIBUTE: 'BEFORE_ATTRIBUTE',
  ATTRIBUTE_NAME: 'ATTRIBUTE_NAME',
  AFTER_ATTRIBUTE_NAME: 'AFTER_ATTRIBUTE_NAME',
  // ... 更多状态
};

// 解析 <div class="container">
// DATA → TAG_OPEN → TAG_NAME → BEFORE_ATTRIBUTE → 
// ATTRIBUTE_NAME → AFTER_ATTRIBUTE_NAME → ATTRIBUTE_VALUE → ...

步骤3:Token生成

解析器生成不同类型的Token:

javascript 复制代码
// 开始标签Token
{
  type: 'startTag',
  tagName: 'div',
  attributes: [
    { name: 'class', value: 'container' }
  ],
  selfClosing: false
}

// 文本Token
{
  type: 'text',
  content: 'Hello World'
}

// 结束标签Token
{
  type: 'endTag',
  tagName: 'div'
}

步骤4:DOM树构建

使用栈结构构建DOM树:

javascript 复制代码
// 伪代码示例
class DOMBuilder {
  constructor() {
    this.stack = [];
    this.document = new Document();
    this.currentNode = this.document;
  }
  
  processToken(token) {
    if (token.type === 'startTag') {
      const element = this.createElement(token);
      this.currentNode.appendChild(element);
      this.stack.push(element);
      this.currentNode = element;
    } else if (token.type === 'endTag') {
      if (this.stack.length > 0) {
        this.stack.pop();
        this.currentNode = this.stack[this.stack.length - 1] || this.document;
      }
    } else if (token.type === 'text') {
      const textNode = this.createTextNode(token.content);
      this.currentNode.appendChild(textNode);
    }
  }
}

2.2 特殊元素处理

预解析(Preload Scanner)

浏览器主线程解析HTML时,预解析器会提前扫描文档,发现需要加载的资源:

html 复制代码
<html>
<head>
  <link rel="stylesheet" href="style.css">  <!-- 预解析器发现 -->
  <script src="app.js"></script>            <!-- 预解析器发现 -->
</head>
<body>
  <img src="image.jpg">                     <!-- 预解析器发现 -->
</body>
</html>

预解析器可以并行下载这些资源,而不阻塞主解析器。

阻塞解析的元素

  1. <script>标签(同步脚本)
html 复制代码
<script src="app.js"></script>
<!-- 解析会暂停,直到JS下载并执行完成 -->

原因: JavaScript可能通过document.write()修改DOM,必须等待执行完成。

优化方案:

  • 使用async属性:异步下载,下载完成后立即执行
  • 使用defer属性:异步下载,延迟到DOM解析完成后执行
  • 使用type="module":ES6模块,默认defer行为
html 复制代码
<!-- 不阻塞解析 -->
<script src="app.js" async></script>
<script src="app.js" defer></script>
<script type="module" src="app.js"></script>
  1. <link rel="stylesheet">标签
html 复制代码
<link rel="stylesheet" href="style.css">
<!-- CSS不会阻塞DOM解析,但会阻塞渲染 -->

CSS阻塞渲染的原因:

  • 避免FOUC(Flash of Unstyled Content)
  • 确保样式计算时CSSOM已构建完成
  1. <img><iframe>等资源

这些资源不会阻塞HTML解析,但会影响页面渲染。

2.3 DOMContentLoaded vs Load事件

javascript 复制代码
// DOM构建完成,但资源可能未加载完成
document.addEventListener('DOMContentLoaded', () => {
  console.log('DOM ready');
});

// 所有资源(图片、样式表等)加载完成
window.addEventListener('load', () => {
  console.log('All resources loaded');
});

事件触发时机:

复制代码
HTML解析开始
    ↓
DOM树构建完成
    ↓
所有defer脚本执行完成 → DOMContentLoaded事件触发
    ↓
CSS加载完成(如果阻塞渲染)
    ↓
所有同步和async脚本执行完成
    ↓
图片等资源加载完成 → load事件触发

注意: DOMContentLoaded不等待图片、样式表、子框架加载完成,但会等待所有defer脚本执行完成。


CSS解析与样式计算

3.1 CSS解析流程
CSS字节流 字符解码 词法分析 生成CSS Token 语法分析 生成CSS规则 构建CSSOM树

CSS词法分析示例:

css 复制代码
/* CSS源码 */
.container {
  width: 100px;
  color: #333;
}

Token序列:

复制代码
IDENT(.container) → LEFT_BRACE → 
IDENT(width) → COLON → NUMBER(100) → UNIT(px) → SEMICOLON →
IDENT(color) → COLON → HASH(#333) → SEMICOLON →
RIGHT_BRACE

3.2 CSSOM树构建

CSSOM(CSS Object Model)是CSS的树形结构表示:

css 复制代码
/* CSS规则 */
body { font-size: 16px; }
.container { width: 100%; }
.container .title { color: red; }

CSSOM树结构:

复制代码
StyleSheet
└── Rule: body
    └── Declaration: font-size: 16px
└── Rule: .container
    └── Declaration: width: 100%
└── Rule: .container .title
    └── Declaration: color: red

3.3 样式计算(Style Calculation)

样式计算是将CSS规则应用到DOM元素的过程:

步骤1:收集样式规则

对于每个DOM元素,收集所有匹配的CSS规则:

javascript 复制代码
// 伪代码
function collectMatchingRules(element) {
  const rules = [];
  
  // 遍历所有样式表
  for (const stylesheet of document.styleSheets) {
    // 遍历所有规则
    for (const rule of stylesheet.cssRules) {
      if (matchesSelector(element, rule.selector)) {
        rules.push(rule);
      }
    }
  }
  
  return rules;
}

步骤2:计算特异性(Specificity)

CSS选择器特异性计算:

css 复制代码
/* 特异性: 0,0,1,0 (1个类选择器) */
.container { }

/* 特异性: 0,0,1,1 (1个类选择器 + 1个元素选择器) */
.container div { }

/* 特异性: 0,1,0,0 (1个ID选择器) */
#header { }

/* 特异性: 1,0,0,0 (内联样式) */
<div style="color: red">

特异性计算规则:

  • 内联样式:1,0,0,0
  • ID选择器:0,1,0,0
  • 类选择器、属性选择器、伪类:0,0,1,0
  • 元素选择器、伪元素:0,0,0,1

步骤3:层叠(Cascade)

按照以下顺序确定最终样式:

  1. 重要性(!important)
  2. 来源(用户样式、作者样式、浏览器默认样式)
  3. 特异性
  4. 源代码顺序

步骤4:继承(Inheritance)

某些CSS属性会从父元素继承:

css 复制代码
body {
  font-size: 16px;  /* 子元素会继承 */
  color: #333;      /* 子元素会继承 */
  width: 100%;      /* 子元素不会继承 */
}

3.4 渲染阻塞

CSS会阻塞渲染,但不会阻塞DOM解析:

html 复制代码
<html>
<head>
  <link rel="stylesheet" href="slow.css">  <!-- 需要3秒加载 -->
</head>
<body>
  <div>这段文字不会立即显示</div>  <!-- 等待CSS加载完成 -->
</body>
</html>

优化策略:

  • 关键CSS内联:将首屏关键样式内联到HTML
  • 媒体查询:<link rel="stylesheet" media="print" href="print.css">
  • 异步加载非关键CSS

JavaScript执行机制

4.1 JavaScript引擎架构

现代JavaScript引擎(V8、SpiderMonkey等)采用多阶段编译:
冷代码 热代码 JS源码 解析器Parser AST抽象语法树 热点检测 解释器Ignition 编译器TurboFan 字节码执行 优化机器码 执行

4.2 JavaScript解析与执行

步骤1:词法分析(Lexical Analysis)

javascript 复制代码
// 源码
const x = 10 + 20;

// Token序列
[
  { type: 'keyword', value: 'const' },
  { type: 'identifier', value: 'x' },
  { type: 'operator', value: '=' },
  { type: 'number', value: 10 },
  { type: 'operator', value: '+' },
  { type: 'number', value: 20 },
  { type: 'punctuator', value: ';' }
]

步骤2:语法分析(Syntax Analysis)

生成AST(抽象语法树):

javascript 复制代码
// AST结构
{
  type: 'VariableDeclaration',
  kind: 'const',
  declarations: [{
    type: 'VariableDeclarator',
    id: { type: 'Identifier', name: 'x' },
    init: {
      type: 'BinaryExpression',
      operator: '+',
      left: { type: 'Literal', value: 10 },
      right: { type: 'Literal', value: 20 }
    }
  }]
}

步骤3:字节码生成与执行

V8引擎使用Ignition解释器生成字节码:

javascript 复制代码
// 伪字节码
LdaConstant [0]        // 加载常量10
Add [1]                // 加上常量20
Star r0                // 存储到寄存器r0

4.3 执行上下文与作用域

执行上下文栈(Call Stack):

javascript 复制代码
function a() {
  console.log('a');
  b();
}

function b() {
  console.log('b');
  c();
}

function c() {
  console.log('c');
}

a();

// 执行上下文栈变化:
// [] → [a] → [a, b] → [a, b, c] → [a, b] → [a] → []

4.4 事件循环(Event Loop)

JavaScript采用事件循环机制处理异步任务:
否 是 否 是 否 是 执行栈 栈空? 检查微任务队列 微任务队列空? 执行微任务 检查宏任务队列 宏任务队列空? 执行宏任务 等待新任务

任务类型:

  • 宏任务(MacroTask):setTimeout、setInterval、I/O操作、UI渲染
  • 微任务(MicroTask):Promise.then、queueMicrotask、MutationObserver

执行顺序示例:

javascript 复制代码
console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

// 输出: 1, 4, 3, 2
// 执行栈 → 微任务 → 宏任务

4.5 JavaScript阻塞解析

同步脚本阻塞:

html 复制代码
<script src="heavy.js"></script>
<!-- HTML解析暂停,等待JS下载和执行 -->
<div>这段内容要等JS执行完才解析</div>

原因:

  • document.write()可能修改DOM
  • 脚本可能访问未解析的DOM节点
  • 脚本可能修改样式,影响渲染

优化方案:

html 复制代码
<!-- 1. 使用async:异步下载,立即执行 -->
<script src="app.js" async></script>

<!-- 2. 使用defer:异步下载,延迟执行 -->
<script src="app.js" defer></script>

<!-- 3. 动态加载 -->
<script>
  const script = document.createElement('script');
  script.src = 'app.js';
  script.async = true;
  document.head.appendChild(script);
</script>

<!-- 4. 使用ES6模块(默认defer) -->
<script type="module" src="app.js"></script>

渲染树构建与布局

5.1 渲染树(Render Tree)构建

渲染树是DOM树和CSSOM树的结合,只包含需要渲染的节点:
DOM树 渲染树 CSSOM树 布局计算

渲染树构建规则:

  1. 只包含可见元素

    • 排除:display: none的元素
    • 包含:visibility: hidden的元素(仍占空间)
    • 包含:opacity: 0的元素
  2. 每个节点包含样式信息

javascript 复制代码
// 渲染树节点结构(简化)
{
  element: <div>,
  computedStyle: {
    width: '100px',
    height: '50px',
    color: 'rgb(51, 51, 51)',
    // ... 所有计算后的样式
  },
  children: [...]
}

构建过程示例:

html 复制代码
<!-- DOM树 -->
<div style="display: none">隐藏</div>
<div class="container">可见</div>
<span>文本</span>
css 复制代码
.container {
  width: 100px;
  height: 50px;
}

渲染树(只包含可见元素):

复制代码
RenderTree
└── RenderDiv (container)
    └── computedStyle: { width: 100px, height: 50px }
└── RenderText (文本)

5.2 布局(Layout/Reflow)

布局是计算每个渲染树节点在视口中的确切位置和大小。

布局流程:
渲染树 布局计算 计算盒模型 计算位置 计算尺寸 生成布局树

盒模型计算:

css 复制代码
.box {
  width: 100px;
  height: 50px;
  padding: 10px;
  border: 2px solid black;
  margin: 5px;
}

计算过程:

复制代码
内容宽度: 100px
+ 内边距: 10px × 2 = 20px
+ 边框: 2px × 2 = 4px
= 元素宽度: 124px

元素宽度: 124px
+ 外边距: 5px × 2 = 10px
= 总占用宽度: 134px

布局算法:

  1. 块级布局(Block Layout)

    • 垂直排列
    • 宽度填满容器
    • 高度由内容决定
  2. 行内布局(Inline Layout)

    • 水平排列
    • 宽度由内容决定
    • 可以换行
  3. Flexbox布局

    • 弹性容器
    • 主轴和交叉轴
    • 复杂的对齐和分布规则
  4. Grid布局

    • 二维网格
    • 行和列的定义
    • 网格项定位

布局优化:

  • 避免强制同步布局(Forced Synchronous Layout)
javascript 复制代码
// ❌ 不好:强制同步布局
const width = element.offsetWidth;  // 触发布局
element.style.width = width + 10 + 'px';  // 再次触发布局

// ✅ 好:批量读取,批量写入
const width = element.offsetWidth;
const height = element.offsetHeight;
// ... 其他读取操作

element.style.width = width + 10 + 'px';
element.style.height = height + 10 + 'px';
// ... 其他写入操作
  • 使用CSS Transform代替修改位置属性
javascript 复制代码
// ❌ 触发布局和绘制
element.style.left = '100px';
element.style.top = '100px';

// ✅ 只触发合成,性能更好
element.style.transform = 'translate(100px, 100px)';

绘制与合成

6.1 绘制(Paint)

绘制是将布局信息转换为实际像素的过程。

绘制流程:
布局树 生成绘制记录 光栅化 生成图层 合成

绘制记录(Paint Records):

绘制记录是绘制指令的列表:

javascript 复制代码
// 伪代码:绘制记录
[
  { type: 'fillRect', x: 0, y: 0, width: 100, height: 50, color: '#fff' },
  { type: 'fillText', text: 'Hello', x: 10, y: 30, font: '16px Arial' },
  { type: 'strokeRect', x: 0, y: 0, width: 100, height: 50, color: '#000' }
]

绘制顺序(Stacking Context):

CSS层叠上下文决定绘制顺序:

css 复制代码
.container {
  z-index: 1;
  position: relative;
}

.overlay {
  z-index: 2;
  position: absolute;
}

绘制顺序规则:

  1. 背景和边框
  2. 负z-index的子元素
  3. 非定位块级元素
  4. 非定位浮动元素
  5. 非定位行内元素
  6. z-index: 0的定位元素
  7. 正z-index的定位元素

6.2 图层(Layer)与合成(Composite)

现代浏览器使用**合成层(Compositing Layer)**优化渲染性能。

图层创建条件:

以下情况会创建新的合成层:

  1. 3D Transform
css 复制代码
.element {
  transform: translateZ(0);  /* 创建新图层 */
}
  1. will-change属性
css 复制代码
.element {
  will-change: transform;  /* 提示浏览器优化 */
}
  1. opacity动画
css 复制代码
.element {
  opacity: 0.5;
  animation: fade 1s;  /* 可能创建图层 */
}
  1. position: fixed(在某些条件下)
css 复制代码
.header {
  position: fixed;  /* 可能创建新图层 */
  /* 注意:不是所有fixed元素都会创建图层,
     通常需要配合transform、opacity等属性,
     或当元素被其他合成层覆盖时 */
}
  1. video、canvas、iframe等元素

合成流程:
多个图层 光栅化 生成位图 图层合成 最终画面

合成线程优化:

合成在合成线程中进行,不阻塞主线程:

javascript 复制代码
// 主线程:修改transform
element.style.transform = 'translateX(100px)';

// 合成线程:独立处理动画
// 不需要主线程参与,60fps流畅动画

性能对比:

属性修改 触发阶段 性能
left, top 布局 → 绘制 → 合成
width, height 布局 → 绘制 → 合成
background-color 绘制 → 合成
transform, opacity 合成

6.3 关键渲染路径(Critical Rendering Path)

关键渲染路径是从HTML、CSS、JavaScript到最终渲染的完整过程:
阻塞 阻塞 HTML DOM CSS CSSOM 渲染树 布局 绘制 合成 JS

优化关键渲染路径:

  1. 减少关键资源数量

    • 内联关键CSS
    • 延迟非关键CSS
    • 使用async/defer加载JS
  2. 减少关键资源大小

    • 压缩CSS/JS
    • 使用Gzip/Brotli
    • 移除未使用的CSS
  3. 缩短关键路径长度

    • 减少DNS查找
    • 使用CDN
    • HTTP/2多路复用

性能优化实践

7.1 资源加载优化

1. 预加载(Preload)

html 复制代码
<!-- 提前加载关键资源 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="app.js" as="script">

2. 预连接(Preconnect)

html 复制代码
<!-- 提前建立连接 -->
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">

3. 预获取(Prefetch)

html 复制代码
<!-- 预取可能需要的资源 -->
<link rel="prefetch" href="next-page.html">

4. 资源提示优先级

html 复制代码
<!-- 最高优先级 -->
<link rel="preload" href="critical.js" as="script">

<!-- 高优先级 -->
<script src="important.js"></script>

<!-- 低优先级 -->
<link rel="prefetch" href="optional.js" as="script">

7.2 渲染优化

1. 避免阻塞渲染

html 复制代码
<!-- ❌ 阻塞渲染 -->
<link rel="stylesheet" href="style.css">
<script src="app.js"></script>

<!-- ✅ 优化 -->
<!-- 关键CSS内联 -->
<style>
  /* 关键样式 */
</style>
<!-- 非关键CSS异步加载 -->
<link rel="preload" href="style.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<!-- JS异步加载 -->
<script src="app.js" defer></script>

2. 减少重排重绘

javascript 复制代码
// ❌ 多次重排
element.style.width = '100px';
element.style.height = '50px';
element.style.left = '10px';
element.style.top = '20px';

// ✅ 使用CSS类
element.className = 'new-style';

// ✅ 使用transform(只触发合成)
element.style.transform = 'translate(10px, 20px) scale(1.1)';

3. 使用虚拟滚动

对于长列表,只渲染可见区域:

javascript 复制代码
// 只渲染可见的100个元素,而不是10000个
const visibleItems = items.slice(startIndex, startIndex + 100);

4. 使用requestAnimationFrame

javascript 复制代码
// ✅ 在下一帧渲染前执行
function animate() {
  // 更新动画
  element.style.transform = `translateX(${x}px)`;
  x += 1;
  
  if (x < 1000) {
    requestAnimationFrame(animate);
  }
}
requestAnimationFrame(animate);

7.3 代码分割与懒加载

1. 动态导入

javascript 复制代码
// 按需加载模块
const module = await import('./heavy-module.js');
module.doSomething();

2. 路由级别的代码分割

javascript 复制代码
// React Router示例
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

3. 图片懒加载

html 复制代码
<!-- 原生懒加载 -->
<img src="image.jpg" loading="lazy" alt="Image">

<!-- Intersection Observer API -->
<script>
  const images = document.querySelectorAll('img[data-src]');
  const imageObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        imageObserver.unobserve(img);
      }
    });
  });
  images.forEach(img => imageObserver.observe(img));
</script>

7.4 缓存策略

1. 浏览器缓存

http 复制代码
# 强缓存
Cache-Control: max-age=3600
Expires: Wed, 21 Oct 2025 07:28:00 GMT

# 协商缓存
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

2. Service Worker缓存

javascript 复制代码
// 缓存策略
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // 缓存优先策略
      return response || fetch(event.request);
    })
  );
});

7.5 性能监控

1. Performance API

javascript 复制代码
// 测量页面加载时间
window.addEventListener('load', () => {
  const perfData = performance.timing;
  const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
  console.log('页面加载时间:', pageLoadTime);
});

// 测量资源加载时间
performance.getEntriesByType('resource').forEach((resource) => {
  console.log(resource.name, resource.duration);
});

2. Web Vitals

javascript 复制代码
// 核心 Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(console.log);  // 累积布局偏移
getFID(console.log);  // 首次输入延迟
getFCP(console.log);  // 首次内容绘制
getLCP(console.log);  // 最大内容绘制
getTTFB(console.log); // 首字节时间

HTTP/3与QUIC协议详解

8.1 HTTP/3的诞生背景

HTTP/2虽然解决了HTTP/1.x的多路复用问题,但仍基于TCP协议,存在以下局限性:

  • 队头阻塞(Head-of-Line Blocking):TCP层的数据包丢失会导致所有流被阻塞
  • 连接建立延迟:TCP三次握手 + TLS握手需要多次往返
  • 网络切换问题:移动设备切换网络时,TCP连接需要重建

HTTP/3基于QUIC协议,在UDP上实现可靠传输,解决了这些问题。

8.2 QUIC协议特性

1. 快速连接建立
客户端 服务器 Client Hello (包含加密信息) Server Hello + 应用数据 首次连接: 1-RTT(1次往返) 后续连接: 0-RTT(如果服务器支持) 客户端 服务器

对比HTTP/2:

  • HTTP/2: TCP握手(1 RTT) + TLS握手(1-2 RTT) = 2-3 RTT(首次连接)
  • HTTP/3: QUIC握手 = 1 RTT(首次连接),0 RTT(后续连接,如果服务器支持)

2. 内置加密

QUIC在传输层内置TLS 1.3,所有数据默认加密:

  • 连接建立过程即加密
  • 防止中间人攻击
  • 保护元数据(包编号等)

3. 连接迁移

移动设备切换网络时,QUIC连接可以无缝迁移:

javascript 复制代码
// 客户端IP从 192.168.1.1 切换到 10.0.0.1
// QUIC连接ID保持不变,连接继续工作
// TCP则需要重新建立连接

4. 多路复用

QUIC实现了真正的多路复用,每个流独立控制:

javascript 复制代码
// HTTP/2: 一个流的数据包丢失,所有流被阻塞
// HTTP/3: 每个流独立,互不影响

8.3 HTTP/3的部署

服务器支持:

nginx 复制代码
# Nginx配置示例
server {
    listen 443 quic reuseport;
    listen 443 ssl http2;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    # 添加Alt-Svc头
    add_header Alt-Svc 'h3=":443"; ma=86400';
}

浏览器检测:

javascript 复制代码
// 检测是否使用HTTP/3
// 注意:浏览器不直接暴露使用的HTTP版本
// 可以通过Network面板查看,或使用服务器日志

// 检查Alt-Svc响应头(服务器可能支持HTTP/3)
fetch('/api/data')
  .then(response => {
    const altSvc = response.headers.get('alt-svc');
    console.log('Alt-Svc:', altSvc);
    // 如果包含h3,表示服务器支持HTTP/3
  });

// Chrome: chrome://net-internals/#http2 可以查看连接详情

Service Worker详解

9.1 Service Worker在页面加载中的作用

Service Worker是运行在浏览器后台的网络代理,可以拦截网络请求:
是 否 是 否 页面请求资源 Service Worker已注册? 拦截请求 正常网络请求 缓存中有? 返回缓存 网络请求 更新缓存 返回给页面

9.2 Service Worker生命周期

1. 注册阶段

javascript 复制代码
// 主线程注册Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => {
      console.log('SW注册成功');
    })
    .catch(error => {
      console.log('SW注册失败:', error);
    });
}

2. 安装阶段(Install)

javascript 复制代码
// sw.js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/index.html',
        '/style.css',
        '/app.js'
      ]);
    })
  );
  // 强制激活新SW,跳过等待阶段
  self.skipWaiting();
});

3. 激活阶段(Activate)

javascript 复制代码
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter(name => name !== 'v1')
          .map(name => caches.delete(name))
      );
    })
  );
  // 立即控制所有客户端
  self.clients.claim();
});

4. 拦截请求(Fetch)

javascript 复制代码
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // 缓存优先策略
      if (response) {
        return response;
      }
      
      // 网络请求
      return fetch(event.request).then((response) => {
        // 缓存响应
        const responseClone = response.clone();
        caches.open('v1').then((cache) => {
          cache.put(event.request, responseClone);
        });
        return response;
      });
    })
  );
});

9.3 缓存策略

1. 缓存优先(Cache First)

javascript 复制代码
// 适用于静态资源
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

2. 网络优先(Network First)

javascript 复制代码
// 适用于需要实时性的数据
self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then(response => {
        const responseClone = response.clone();
        caches.open('v1').then(cache => {
          cache.put(event.request, responseClone);
        });
        return response;
      })
      .catch(() => caches.match(event.request))
  );
});

3. 网络优先,缓存回退(Network First with Cache Fallback)

javascript 复制代码
self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then(response => {
        // 网络可用,使用网络响应
        if (response && response.status === 200) {
          const responseClone = response.clone();
          caches.open('v1').then(cache => {
            cache.put(event.request, responseClone);
          });
        }
        return response;
      })
      .catch(() => {
        // 网络不可用,使用缓存
        return caches.match(event.request);
      })
  );
});

4. 仅网络(Network Only)

javascript 复制代码
// 不缓存,直接请求网络
self.addEventListener('fetch', (event) => {
  event.respondWith(fetch(event.request));
});

5. 仅缓存(Cache Only)

javascript 复制代码
// 只使用缓存,不请求网络
self.addEventListener('fetch', (event) => {
  event.respondWith(caches.match(event.request));
});

9.4 Service Worker对页面加载的影响

优势:

  • 离线访问:页面和资源可以被缓存,离线时也能访问
  • 加速加载:缓存资源直接从本地读取,速度快
  • 减少网络请求:减少对服务器的压力

注意事项:

  • Service Worker注册和激活需要时间
  • 缓存更新需要策略控制
  • 存储空间限制(通常5-10%的磁盘空间)

浏览器安全机制

10.1 同源策略(Same-Origin Policy)

同源策略是浏览器最基础的安全机制,限制一个源的文档或脚本如何与另一个源的资源交互。

同源定义:

  • 协议相同(http/https)
  • 域名相同
  • 端口相同
javascript 复制代码
// 同源示例
https://www.example.com:443/page1  ✅
https://www.example.com:443/page2  ✅

// 不同源
https://www.example.com:443/page1  ❌
http://www.example.com:80/page1    (协议不同)

https://www.example.com:443/page1  ❌
https://api.example.com:443/page1  (域名不同)

https://www.example.com:443/page1  ❌
https://www.example.com:8080/page1 (端口不同)

受限操作:

  • Cookie、LocalStorage、IndexedDB访问
  • AJAX请求
  • DOM操作(iframe跨域)

10.2 CORS(跨源资源共享)

CORS允许服务器声明哪些源可以访问资源。

简单请求:

javascript 复制代码
// 前端代码
fetch('https://api.example.com/data', {
  method: 'GET',
  headers: {
    'Content-Type': 'text/plain'
  }
});
http 复制代码
# 服务器响应头
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type

预检请求(Preflight):

javascript 复制代码
// 复杂请求会先发送OPTIONS请求
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'
  },
  body: JSON.stringify({ data: 'test' })
});
http 复制代码
# 预检请求
OPTIONS /data HTTP/1.1
Origin: https://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header

# 服务器响应
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 86400

10.3 内容安全策略(CSP)

CSP通过白名单机制,限制页面可以加载的资源。

基本用法:

html 复制代码
<!-- 通过meta标签 -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' https://cdn.example.com;
               style-src 'self' 'unsafe-inline';
               img-src 'self' https: data:;
               connect-src 'self' https://api.example.com;">

<!-- 通过HTTP头 -->
Content-Security-Policy: default-src 'self'; script-src 'self'

CSP指令:

javascript 复制代码
// default-src: 默认策略
'default-src': "'self'"

// script-src: 限制JavaScript
'script-src': "'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com"

// style-src: 限制CSS
'style-src': "'self' 'unsafe-inline'"

// img-src: 限制图片
'img-src': "'self' https: data:"

// connect-src: 限制AJAX/WebSocket
'connect-src': "'self' https://api.example.com"

// font-src: 限制字体
'font-src': "'self' https://fonts.example.com"

// frame-src: 限制iframe
'frame-src': "'none'"

CSP报告:

html 复制代码
<!-- 只报告,不阻止 -->
Content-Security-Policy-Report-Only: default-src 'self'; 
                                      report-uri /csp-report

浏览器缓存机制详解

11.1 缓存类型

浏览器缓存分为强缓存协商缓存
无 有 未过期 过期 未修改 已修改 浏览器请求资源 缓存中有? 请求服务器 是否过期? 使用强缓存 携带验证信息请求服务器 资源是否修改? 304 Not Modified
使用协商缓存 200 OK
返回新资源

11.2 强缓存(Strong Cache)

强缓存由响应头控制,浏览器直接使用缓存,不请求服务器。

Cache-Control指令:

http 复制代码
# 缓存1小时
Cache-Control: max-age=3600

# 缓存1小时,且可被代理服务器缓存
Cache-Control: public, max-age=3600

# 只能被浏览器缓存,不能被代理服务器缓存
Cache-Control: private, max-age=3600

# 不缓存
Cache-Control: no-cache

# 不存储缓存
Cache-Control: no-store

# 缓存过期后必须验证
Cache-Control: must-revalidate

# 缓存未过期前可使用,过期后需验证(允许使用过期缓存)
Cache-Control: stale-while-revalidate=3600

# 允许使用过期缓存的时长
Cache-Control: stale-if-error=86400

Expires(HTTP/1.0):

http 复制代码
Expires: Wed, 21 Oct 2025 07:28:00 GMT

优先级: Cache-Control > Expires

11.3 协商缓存(Negotiation Cache)

协商缓存需要向服务器验证资源是否修改。

Last-Modified / If-Modified-Since:

http 复制代码
# 服务器响应
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

# 客户端请求
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

# 服务器响应(未修改)
HTTP/1.1 304 Not Modified

ETag / If-None-Match:

http 复制代码
# 服务器响应
ETag: "abc123def456"

# 客户端请求
If-None-Match: "abc123def456"

# 服务器响应(未修改)
HTTP/1.1 304 Not Modified

优先级: ETag > Last-Modified

11.4 缓存最佳实践

javascript 复制代码
// 静态资源:长期缓存 + 版本号
Cache-Control: public, max-age=31536000, immutable
// URL: /app.js?v=1.2.3

// HTML:不缓存或短期缓存
Cache-Control: no-cache
// 或
Cache-Control: max-age=0, must-revalidate

// API数据:不缓存或短期缓存
Cache-Control: private, max-age=60

JavaScript内存管理

12.1 内存生命周期

分配内存 使用内存 释放内存

JavaScript内存分配:

javascript 复制代码
// 自动分配
const number = 123;           // 数字
const string = 'text';        // 字符串
const object = { a: 1 };      // 对象
const array = [1, 2, 3];      // 数组

12.2 垃圾回收(Garbage Collection)

现代JavaScript引擎使用**标记-清除(Mark-and-Sweep)**算法:

1. 标记阶段:

javascript 复制代码
// 从根对象(全局对象)开始标记所有可达对象
// 根对象: window, globalThis, 活动函数的作用域链

2. 清除阶段:

javascript 复制代码
// 清除所有未标记的对象

引用计数问题(已淘汰):

javascript 复制代码
// 循环引用导致内存泄漏
function createCycle() {
  const obj1 = {};
  const obj2 = {};
  obj1.ref = obj2;
  obj2.ref = obj1;  // 循环引用
  return obj1;
}

12.3 内存泄漏常见场景

1. 全局变量:

javascript 复制代码
// ❌ 泄漏
function leak() {
  leakedVar = 'I am leaked';
}

// ✅ 修复
function noLeak() {
  const localVar = 'I am local';
}

2. 事件监听器未移除:

javascript 复制代码
// ❌ 泄漏
element.addEventListener('click', handler);
// 元素被删除,但监听器仍在

// ✅ 修复
element.addEventListener('click', handler);
element.removeEventListener('click', handler);
// 或使用AbortController
const controller = new AbortController();
element.addEventListener('click', handler, { signal: controller.signal });
controller.abort();

3. 闭包:

javascript 复制代码
// ❌ 泄漏
function attachHandler() {
  const largeData = new Array(1000000).fill('data');
  element.addEventListener('click', () => {
    console.log('clicked');
    // largeData被闭包引用,无法释放
  });
}

// ✅ 修复
function attachHandler() {
  element.addEventListener('click', function handler() {
    console.log('clicked');
    element.removeEventListener('click', handler);
  });
}

4. 定时器未清除:

javascript 复制代码
// ❌ 泄漏
const timer = setInterval(() => {
  // ...
}, 1000);

// ✅ 修复
const timer = setInterval(() => {
  // ...
  clearInterval(timer);
}, 1000);

12.4 内存监控

javascript 复制代码
// 使用Performance API监控内存
if (performance.memory) {
  console.log('已用堆内存:', performance.memory.usedJSHeapSize);
  console.log('总堆内存:', performance.memory.totalJSHeapSize);
  console.log('堆内存限制:', performance.memory.jsHeapSizeLimit);
}

// Chrome DevTools Memory Profiler
// Performance -> Memory -> 录制内存快照

首屏渲染指标详解

13.1 核心Web Vitals

1. LCP(Largest Contentful Paint)- 最大内容绘制

测量页面最大内容元素渲染完成的时间。

javascript 复制代码
// 测量LCP
import { getLCP } from 'web-vitals';

getLCP((metric) => {
  console.log('LCP:', metric.value);
  // 良好: < 2.5s
  // 需要改进: 2.5s - 4s
  // 差: > 4s
});

优化LCP:

  • 优化服务器响应时间
  • 优化关键渲染路径
  • 移除阻塞渲染的资源
  • 预加载关键资源

2. FID(First Input Delay)- 首次输入延迟

测量用户首次与页面交互到浏览器响应该交互的时间。

注意: FID和INP是不同的指标。INP(Interaction to Next Paint)是更全面的交互指标,将逐步取代FID成为Core Web Vitals的一部分。

javascript 复制代码
// 测量FID
import { getFID } from 'web-vitals';

getFID((metric) => {
  console.log('FID:', metric.value);
  // 良好: < 100ms
  // 需要改进: 100ms - 300ms
  // 差: > 300ms
});

// 测量INP(更全面的交互指标)
import { onINP } from 'web-vitals';

onINP((metric) => {
  console.log('INP:', metric.value);
  // 良好: < 200ms
  // 需要改进: 200ms - 500ms
  // 差: > 500ms
});

优化FID/INP:

  • 减少JavaScript执行时间
  • 代码分割和懒加载
  • 使用Web Worker处理耗时任务
  • 优化第三方脚本

3. CLS(Cumulative Layout Shift)- 累积布局偏移

测量页面视觉稳定性。

javascript 复制代码
// 测量CLS
import { getCLS } from 'web-vitals';

getCLS((metric) => {
  console.log('CLS:', metric.value);
  // 良好: < 0.1
  // 需要改进: 0.1 - 0.25
  // 差: > 0.25
});

优化CLS:

  • 为图片和视频设置尺寸属性
  • 不要在现有内容上方插入内容
  • 使用transform动画代替位置属性动画
  • 预留广告位空间

13.2 其他重要指标

1. FCP(First Contentful Paint)- 首次内容绘制

javascript 复制代码
// 测量FCP
import { getFCP } from 'web-vitals';

getFCP((metric) => {
  console.log('FCP:', metric.value);
  // 良好: < 1.8s
});

2. TTI(Time to Interactive)- 可交互时间

javascript 复制代码
// 使用Performance Observer
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.name === 'first-contentful-paint') {
      console.log('FCP:', entry.startTime);
    }
  }
});
observer.observe({ entryTypes: ['paint', 'navigation'] });

3. TBT(Total Blocking Time)- 总阻塞时间

测量主线程被阻塞的总时间。


浏览器调试技巧

14.1 Chrome DevTools性能分析

1. Performance面板:

javascript 复制代码
// 录制页面加载过程
// 1. 打开DevTools -> Performance
// 2. 点击Record按钮
// 3. 刷新页面
// 4. 停止录制
// 5. 分析时间线

关键指标查看:

  • FPS: 帧率(绿色表示60fps)
  • CPU: CPU使用率
  • 网络: 网络请求时间线
  • 主线程: 主线程活动

2. Network面板:

javascript 复制代码
// 分析资源加载
// - 查看Waterfall(瀑布图)
// - 查看请求头/响应头
// - 查看资源大小和加载时间
// - 模拟慢速网络(Throttling)

3. Memory面板:

javascript 复制代码
// 内存分析
// 1. 录制堆快照(Heap Snapshot)
// 2. 对比多个快照,找出内存泄漏
// 3. 查看对象分配时间线(Allocation Timeline)

14.2 性能API使用

javascript 复制代码
// 测量代码执行时间
performance.mark('start');
// ... 代码执行
performance.mark('end');
performance.measure('duration', 'start', 'end');

const measure = performance.getEntriesByName('duration')[0];
console.log('执行时间:', measure.duration);

// 测量资源加载
performance.getEntriesByType('resource').forEach((resource) => {
  console.log(resource.name, resource.duration);
});

// Navigation Timing API
const timing = performance.timing;
console.log('DNS查询时间:', timing.domainLookupEnd - timing.domainLookupStart);
console.log('TCP连接时间:', timing.connectEnd - timing.connectStart);
console.log('页面加载时间:', timing.loadEventEnd - timing.navigationStart);

14.3 渲染性能优化工具

javascript 复制代码
// 使用requestIdleCallback处理非关键任务
requestIdleCallback(() => {
  // 低优先级任务
  processAnalytics();
});

// 使用requestAnimationFrame优化动画
function animate() {
  // 在下一帧前执行
  updateAnimation();
  requestAnimationFrame(animate);
}

移动端浏览器特殊考虑

15.1 移动端性能特点

1. 硬件限制:

  • CPU性能较弱
  • 内存有限(通常2-4GB)
  • 网络不稳定(4G/5G/WiFi切换)

2. 渲染差异:

  • 像素密度高(Retina屏)
  • 触摸事件替代鼠标事件
  • 视口缩放机制

15.2 移动端优化策略

1. 视口设置:

html 复制代码
<!-- 正确的viewport设置 -->
<meta name="viewport" 
      content="width=device-width, 
               initial-scale=1.0, 
               maximum-scale=5.0, 
               user-scalable=yes">

2. 触摸优化:

css 复制代码
/* 启用触摸优化 */
.element {
  touch-action: manipulation; /* 禁用双击缩放 */
  -webkit-tap-highlight-color: transparent; /* 移除点击高亮 */
}

3. 移动端网络优化:

javascript 复制代码
// 检测网络状态
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

if (connection) {
  console.log('网络类型:', connection.effectiveType); // 4g, 3g, 2g, slow-2g
  console.log('下行速度:', connection.downlink);
  console.log('RTT:', connection.rtt);
  
  // 根据网络状态调整策略
  if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
    // 加载低质量图片,减少资源
  }
}

4. 移动端内存管理:

javascript 复制代码
// 更严格的内存管理
// - 及时清理不用的对象
// - 使用WeakMap/WeakSet
// - 限制图片大小和数量
// - 使用虚拟列表

总结

完整加载流程回顾

浏览器页面加载是一个复杂而精密的过程,涉及多个阶段:

  1. 导航阶段:DNS解析 → TCP连接 → HTTP请求 → 响应接收
  2. 解析阶段:HTML解析 → DOM构建 → CSS解析 → CSSOM构建 → JS执行
  3. 渲染阶段:渲染树构建 → 布局计算 → 绘制 → 合成 → 显示

关键要点

  1. HTML解析是流式的,可以边下载边解析
  2. CSS会阻塞渲染,但不阻塞DOM解析
  3. 同步JS会阻塞解析,使用async/defer优化
  4. 布局和绘制是昂贵的,避免频繁触发
  5. 合成层优化动画性能,使用transform和opacity
  6. 关键渲染路径优化是性能优化的核心

优化建议

  • ✅ 内联关键CSS,延迟非关键CSS
  • ✅ 使用async/defer加载JS
  • ✅ 减少DOM操作,批量更新
  • ✅ 使用transform代替位置属性
  • ✅ 图片懒加载和代码分割
  • ✅ 合理使用缓存策略
  • ✅ 监控性能指标,持续优化

技术趋势

  • HTTP/3 (QUIC):更快的连接建立
  • WebAssembly:高性能计算
  • Streaming HTML:服务端流式渲染
  • Partial Hydration:部分水合,减少JS执行时间
  • Edge Computing:边缘计算,减少延迟

理解浏览器加载原理,不仅能帮助我们编写更高效的代码,还能在遇到性能问题时快速定位和解决。持续关注浏览器技术的发展,保持学习,是前端开发者不断进步的关键。


参考资料


本文档基于现代浏览器(Chrome、Firefox、Safari、Edge)的实现原理编写,具体细节可能因浏览器版本而异。

相关推荐
FreeBuf_2 小时前
Next.js 发布扫描工具:检测并修复受 React2Shell 漏洞(CVE-2025-66478)影响的应用
开发语言·javascript·ecmascript
LYFlied2 小时前
在AI时代,前端开发者如何构建全栈开发视野与核心竞争力
前端·人工智能·后端·ai·全栈
用户47949283569152 小时前
我只是给Typescript提个 typo PR,为什么还要签协议?
前端·后端·开源
馬致远2 小时前
Vue -组件入门
javascript·vue.js·ecmascript
程序员爱钓鱼2 小时前
Next.js SSR 项目生产部署全攻略
前端·next.js·trae
程序员爱钓鱼2 小时前
使用Git 实现Hugo热更新部署方案(零停机、自动上线)
前端·next.js·trae
a伊雪3 小时前
c++ 引用参数
c++·算法
御形封灵3 小时前
基于原生table实现单元格合并、增删
开发语言·javascript·ecmascript
颜颜yan_3 小时前
DevUI + Vue 3 入门实战教程:从零构建AI对话应用
前端·vue.js·人工智能