跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptNode.js大前端

前端核心知识点梳理与面试复习指南

综述由AI生成系统梳理了前端开发的核心知识点,涵盖网络基础(URL、DNS、TCP/IP、HTTP/HTTPS)、浏览器渲染原理(DOM、CSSOM、重排重绘)、JavaScript 引擎机制(V8、事件循环)以及构建工具(Webpack、Vite)的配置与优化。文章结合面试场景,详细解析了跨域解决方案、缓存策略、状态码含义及性能优化实践,适合前端开发者复习与进阶。

黑客发布于 2026/4/6更新于 2026/5/2023 浏览

一:从输入 URL 到页面渲染

1. URL 的标准组成部分

文章配图

一个完整的 URL 结构如下:scheme://host:port/path?query#fragment

URI 用字符串标识某一互联网资源,而 URL 表示资源的地点(互联网上所处的位置)。可见 URL 是 URI 的子集。

文章配图

URI 和 URL 的区别?
  • URI (Uniform Resource Identifier) 是统一资源标识符,是一个大概念。
  • URL (Uniform Resource Locator) 是统一资源定位符,它不仅标识资源,还提供了找到资源的方式(比如协议)。可以理解为 URL 是 URI 的子集。

为什么 URL 中有些字符会被转义(如 %20)?

  • URL 只能使用 ASCII 字符集。特殊字符(如空格、中文)必须通过 encodeURI 或 encodeURIComponent 进行编码。

1. 什么是'同源'?

根据我们刚刚讨论的 URL 组成,只有当两个 URL 的 协议 (Protocol)、域名 (Domain) 和 端口 (Port) 均相同时,才被称为同源。

  • https://a.com/page1 和 https://a.com/page2 —— 同源
  • http://a.com 和 https://a.com —— 跨域(协议不同)
  • https://a.com 和 https://b.com —— 跨域(域名不同)
  • https://a.com 和 https://a.com:8080 —— 跨域(端口不同)
2. 常见的跨域解决方案

在面试中,你通常需要给出以下三种最核心的方案:

A. CORS (Cross-Origin Resource Sharing) - 最主流

这是 W3C 标准,由后端通过设置 HTTP 响应头来告诉浏览器:'我允许这个源访问我的资源'。

  • 核心响应头:Access-Control-Allow-Origin: *(或指定的域名)。
  • 简单请求 vs 预检请求:对于复杂请求(如 PUT、DELETE 或自定义 Header),浏览器会先发一个 方法的请求,称为预检请求。
OPTIONS
B. JSONP (JSON with Padding) - 兼容老旧浏览器

利用了 <script> 标签不受同源策略限制的特性。

  • 限制:仅支持 GET 请求。
  • 前端构建工具配置(如 Vite/Webpack):

原理代码实现:

/**
 * 模拟一个简单的 JSONP 实现
 */
function jsonp({ url, params, callbackName }) {
  return new Promise((resolve) => {
    // 1. 创建 script 标签
    const script = document.createElement('script');
    // 2. 将回调函数挂载到 window,供服务端调用
    window[callbackName] = function(data) {
      resolve(data);
      document.body.removeChild(script); // 善后工作
      delete window[callbackName];
    };
    // 3. 构建 URL,带上 callback 参数
    const query = { ...params, callback: callbackName };
    const queryString = Object.keys(query).map(key => `${key}=${query[key]}`).join('&');
    script.src = `${url}?${queryString}`;
    // 4. 插入页面,触发请求
    document.body.appendChild(script);
  });
}
// 使用示例
// jsonp({ url: 'http://api.test.com/data', callbackName: 'handleRes' }).then(res => console.log(res));
C. Proxy (代理) - 开发环境最常用

通过'中间人'绕过浏览器限制。浏览器访问同源的代理服务器,代理服务器再去请求目标服务器(服务器之间没有同源策略)。

// vite.config.js 示例
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://backend-api.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
};
3. 面试官深挖:Cookie 如何在跨域中携带?

这是一个高频追问点。默认情况下,跨域请求不带 Cookie。

  1. 前端:在 Ajax/Fetch 请求中设置 withCredentials = true。
  2. 后端:设置响应头 Access-Control-Allow-Credentials: true。
  3. 注意:此时 Access-Control-Allow-Origin不能设为通配符 *,必须指定具体域名。
总结建议

在面试时,你可以这样总结:'跨域是浏览器的安全屏障,解决它的核心思路要么是让服务器明确许可(CORS),要么是利用标签特性(JSONP),要么是避开浏览器环境(Proxy)。'

2. DNS 解析过程

文章配图

1. DNS 解析过程:将域名变为 IP 地址

当你输入 www.example.com 时,计算机并不认识这个字符串,它需要通过 DNS(域名系统)找到对应的 IP 地址。

详细步骤(递归 + 迭代):

  1. 浏览器缓存/操作系统缓存:首先检查浏览器自身是否有该域名的解析记录,如果没有,再检查操作系统的 hosts 文件。
  2. 本地 DNS 服务器(LDNS):通常是你接入的网络服务商(ISP)。
  3. 根域名服务器(Root Nameserver):LDNS 如果没有缓存,会去问根服务器:'我知道 .com 在哪吗?'。
  4. 顶级域名服务器(TLD Nameserver):根服务器指向 .com 服务器,LDNS 再去问 .com 服务器:"example.com 在哪?'。
  5. 权威域名服务器(Authoritative Nameserver):最后找到负责 example.com 的权威服务器,拿到具体的 IP 地址,返回给浏览器并缓存。

3. TCP/IP 分层管理:数据是如何打包的?

在建立连接前,我们要理解数据是怎么通过网络栈传输的。TCP/IP 通常分为四层(或 OSI 七层模型,面试常考四层):

文章配图

1. 应用层:

应用层决定了向用户提供应用服务时通信的活动。TCP/IP 协议族内预存了各类通用的应用服务。比如,FTP (File Transfer Protocol, 文件传输协议) 和 DNS (Domain Name System, 域名系统) 服务就是其中两类。HTTP 协议也处于该层。

2. 传输层:

传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输。在传输层有两个性质不同的协议:TCP (Transmission Control Protocol, 传输控制协议) 和 UDP (User Data Protocol, 用户数据报协议)。

3. 网络层 (又名网络互连层)

网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数据单位。该层规定了通过怎样的路径 (所谓的传输路线) 到达对方计算机,并把数据包传送给对方。与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的作用就是在众多的选项内选择一条传输路线。

4. 链路层 (又名数据链路层,网络接口层)

用来处理连接网络的硬件部分。包括控制操作系统、硬件的设备驱动、NIC (Network Interface Card, 网络适配器,即网卡),及光纤等物理可见部分 (还包括连接器等一切传输媒介)。硬件上的范畴均在链路层的作用范围之内。

图解

文章配图

文章配图


4. TCP 三次握手:确保双方都有收发能力

拿到 IP 后,客户端需要与服务器建立 TCP 连接。为什么是三次?因为要确保双向通信链路都是通畅的。

文章配图

  • 第一次握手:客户端发送 SYN (Synchronize) 包,序列号为 x。 状态:客户端进入 SYN_SENT。服务器确认了'客户端发送能力正常'。
  • 第二次握手:服务器返回 SYN + ACK (Acknowledgment) 包,序列号 y,确认号 x+1。 状态:服务器进入 SYN_RCVD。客户端确认了'服务器接收和发送能力都正常'。
  • 第三次握手:客户端发送 ACK 包,序列号 x+1,确认号 y+1。 状态:双方进入 ESTABLISHED。服务器确认了'客户端接收能力正常'。

5. 四次挥手的详细过程

想象客户端(Client)主动发起断开请求:

  • 第一次挥手 (FIN):客户端发送一个 FIN (Finish) 报文,用来告诉服务器:'我没有数据要发给你了,我要关闭发送通道'。 状态:客户端进入 FIN_WAIT_1 状态。
  • 第二次挥手 (ACK):服务器收到 FIN,回复一个 ACK。意思是:'收到了,但我可能还有数据没发完,你等我一下'。 状态:服务器进入 CLOSE_WAIT,客户端进入 FIN_WAIT_2。此时连接处于'半关闭'状态。
  • 第三次挥手 (FIN):服务器处理完最后的数据,向客户端发送 FIN。意思是:'好了,我也发完了,我也要关了'。 状态:服务器进入 LAST_ACK。
  • 第四次挥手 (ACK):客户端收到服务器的 FIN,回复最后一个 ACK。意思是:'收到,祝好'。 状态:客户端进入 TIME_WAIT 状态,等待 2MSL 后彻底关闭。服务器收到 ACK 后立即 CLOSED。
面试高频追问:为什么会有 TIME_WAIT?

这是四次挥手最常被问到的技术细节。客户端在发送完最后一个 ACK 后,并不会立即关掉连接,而是要等待 2MSL(Maximum Segment Lifetime,报文最大生存时间)。

原因有两个:

  1. 确保最后的 ACK 能够到达服务器:如果最后一个 ACK 丢了,服务器会超时重传第三次的 FIN。客户端只有在 TIME_WAIT 期间才能重发 ACK。
  2. 防止'已失效的请求'干扰:等待足够长的时间,让本次连接产生的所有报文都在网络中消失。这样下次建立相同 IP 和端口的连接时,就不会收到上一次连接残留的旧数据。

代码与性能层面:CLOSE_WAIT 过多怎么办?

作为前端或全栈开发者,如果监控发现服务器出现大量 CLOSE_WAIT 状态,通常是因为程序 Bug。

  • 原因:对方发了 FIN,但你的代码没有调用 close() 方法关闭 Socket(例如后端连接池没释放,或者长连接逻辑出错)。
  • 后果:会占用大量文件描述符,导致新连接无法建立。

6. HTTP 缓存策略

HTTP 缓存就是:浏览器把请求过的资源存起来,下次用的时候直接从本地拿,不再麻烦服务器。**

1. 缓存的整体流程

当浏览器发起请求时,它会遵循以下逻辑:

  1. 先看强缓存:如果命中了,直接用,不发请求到服务器。
  2. 再看协商缓存:如果没有强缓存或已过期,就带着'凭证'去问服务器:'我这资源还能用吗?'。
  3. 返回结果:
    • 服务器说'没变':返回 304 Not Modified,浏览器继续用本地的。
    • 服务器说'变了':返回 200 OK 和新内容

2. 强缓存 (Strong Cache)

特点:不需要发送 HTTP 请求,直接从内存 (from memory cache) 或磁盘 (from disk cache) 读取。

关键响应头:
  • Expires (HTTP/1.0):
    • 值是一个绝对时间(如 Expires: Wed, 21 Oct 2025 07:28:00 GMT)。
    • 缺点:受限于客户端本地时间,如果用户改了系统时间,缓存会失效。
  • Cache-Control (HTTP/1.1) - 推荐:
    • 使用相对时间,优先级高于 Expires。
    • 常用指令:
      • max-age=3600:缓存 1 小时。
      • no-cache:不使用强缓存,直接进入协商缓存阶段。
      • no-store:完全不缓存,每次都要重新下载。
3. 协商缓存 (Negotiation Cache)

特点:必须发请求到服务器,由服务器决定是否使用缓存。

文章配图


4. 模拟代码场景:如何设置缓存?

如果你在写一个 Node.js (Express) 后端,你可以这样控制缓存:

const express = require('express');
const app = express();
app.get('/static/logo.png', (req, res) => {
  // 设置强缓存:1 年 (单位:秒)
  res.setHeader('Cache-Control', 'public, max-age=31536000');
  // 或者设置协商缓存 (Express 默认会自动处理 ETag)
  // res.setHeader('Cache-Control', 'no-cache');
  res.sendFile(__dirname + '/logo.png');
});
app.listen(3000);
5. 面试官杀手锏:用户操作对缓存的影响

这是很多候选人会忽略的细节:

  • 地址栏回车 / 链接跳转:有效,强缓存和协商缓存都正常工作。
  • F5 刷新:失效。浏览器会在请求头带上 Cache-Control: max-age=0,跳过强缓存,直接发起协商缓存。
  • Ctrl + F5 (强制刷新):全部失效。跳过所有缓存,直接从服务器拉取最新的。
总结建议

在面试中谈论缓存时,你可以顺便带出 '大前端部署实践':

'通常我们会给 HTML 设置 no-cache(走协商缓存),而给静态资源(JS/CSS/图片)设置超长强缓存。为了更新这些资源,我们会给文件名加上 Content Hash。这样只要代码变了,文件名就变,浏览器就会请求新文件,而没变的文件依然能秒开。'


7. 状态码

1. 状态码分类概览

文章配图

2xx - 成功
  • 200 OK:请求成功,有返回内容。
  • 204 No Content:请求成功,但没有响应主体(常用于 OPTIONS 预检请求或删除操作)。
  • 206 Partial Content:客户端发送了范围请求(Range Header),服务器成功返回了部分内容。常用于断点续传或大视频分段加载。
3xx - 重定向
  • 301 Moved Permanently:永久重定向。浏览器会自动缓存新地址,下次直接跳新地址。
  • 302 Found:临时重定向。下次访问还是会请求原地址。
  • 304 Not Modified:重点! 协商缓存命中,服务器告诉浏览器:'东西没变,你直接用本地缓存吧'。
4xx - 客户端错误
  • 400 Bad Request:请求语法错误(比如 JSON 格式写错了)。
  • 401 Unauthorized:未授权,需要身份验证(比如没登录)。
  • 403 Forbidden:服务器理解请求,但拒绝执行(比如你有登录,但没有管理员权限访问该页面)。
  • 404 Not Found:资源不存在。
  • 405 Method Not Allowed:方法不支持(比如接口只准 POST,你用了 GET)。
5xx - 服务器错误
  • 500 Internal Server Error:后端代码报错了。
  • 502 Bad Gateway:充当网关或代理的服务器(如 Nginx)尝试执行请求时,从上游服务器接收到无效响应。
  • 503 Service Unavailable:服务器超载或停机维护。
  • 504 Gateway Timeout:网关超时,上游服务器没在规定时间内返回数据。
2. 面试官进阶:301 和 302 对 SEO 的影响?
  • 301 (永久):搜索引擎在抓取时会把旧地址的权重(PR 值)转移到新地址。如果你换了新域名,一定要做 301。
  • 302 (临时):权重不会转移。如果滥用 302,可能会被搜索引擎判定为 URL 劫持。
总结建议

当面试官问你状态码时,不仅要说出数字含义,最好能结合实际业务场景。比如谈到 206 聊聊视频大文件下载,谈到 304 聊聊刚才说的 HTTP 缓存,谈到 401/403 聊聊权限控制。

8. HTTP 和 HTTPS 的区别

1. 核心区别汇总

文章配图

2. HTTPS 的'安全'是如何实现的?

HTTPS 并不是一种新的协议,而是 HTTP + SSL/TLS。它主要解决了 HTTP 的三个安全问题:

  1. 机密性(Encryption):防止数据被中间人窃听。
  2. 完整性(Integrity):防止数据在传输过程中被篡改。
  3. 身份认证(Authentication):确认你访问的网站确实是'官宣'的那个,而不是钓鱼网站。

3. HTTPS 的握手过程(核心考点)

这是面试官最喜欢问的细节。它结合了对称加密和非对称加密的优点。

  1. 客户端请求:客户端(浏览器)向服务器发起 HTTPS 请求,连接到 443 端口,发送支持的加密算法列表。
  2. 服务器响应:服务器选择一套加密算法,并发送自己的数字证书(包含服务器公钥)。
  3. 客户端验证:
    • 浏览器检查证书是否过期、颁发机构是否可信。
    • 如果验证通过,客户端生成一个随机数(预主密钥)。
  4. 非对称加密传输密钥:客户端用服务器的公钥加密这个随机数,发给服务器。
  5. 服务器解密:服务器用自己的私钥解密,得到这个随机数。
  6. 对称加密传输数据:现在双方都有了这个随机数,它将作为对称加密的密钥。此后的所有数据传输都使用这个随机数进行加密。

为什么这样设计? 非对称加密(公钥/私钥)虽然安全,但速度慢。对称加密(一个密钥)速度快,但密钥传输不安全。结论:用非对称加密来安全地传输对称加密的密钥,然后用对称加密来传数据。


4. 代码层面:前端需要做什么?

作为前端开发者,你不需要编写加密算法,但你需要知道:

  • Mixed Content 警告:如果你的 HTTPS 页面中引用了 HTTP 的静态资源(如图片、脚本),浏览器会报错或拦截。
  • HSTS (HTTP Strict Transport Security):一种安全策略,强制浏览器只使用 HTTPS 与服务器通信。
// 在 Node.js (Express) 中开启 HTTPS 的简单示例
const https = require('https');
const fs = require('fs');
const express = require('express');
const options = {
  key: fs.readFileSync('server.key'), // 私钥
  cert: fs.readFileSync('server.crt') // 证书
};
const app = express();
https.createServer(options, app).listen(443, () => {
  console.log('HTTPS Server running on port 443');
});

5. 面试官可能追问:什么是中间人攻击(MITM)?

如果黑客伪造了证书,而用户忽略了浏览器的安全警告点击了'继续访问',黑客就可以解密你的数据。这就是为什么证书的有效性验证至关重要。


二:浏览器渲染原理

1. 渲染模式的分水岭:后端返回了什么?


1. 服务端渲染 (SSR - Server Side Rendering)
  • 后端返回:一个包含了完整 DOM 结构的 HTML 字符串。
  • 浏览器起点:拿到 HTML 后立即开始解析并显示内容。
  • 特点:SEO 友好,首屏加载快(白屏时间短)。
  • 现代框架:Next.js (React), Nuxt.js (Vue)。
2. 客户端渲染 (CSR - Client Side Rendering)
  • 后端返回:一个几乎为空的 HTML 壳子(通常只有一个 <div></div>)和一堆 JS 文件。
  • 浏览器起点:必须等待 JS 下载并执行完毕后,由 JS 动态创建 DOM 节点。
  • 特点:用户体验流畅(页面切换无刷新),但首屏可能较慢。
  • 现代框架:普通的 Vue-cli 或 Create-React-App 项目。

2. 浏览器渲染的详细流水线 (The Critical Rendering Path)

无论数据是怎么来的,一旦浏览器拿到了 HTML、CSS 和 JS,就会进入关键渲染路径。这是面试中最核心的流程:

第一步:构建对象模型

1. 构建 DOM 树:浏览器将 HTML 字节流解析为一个个令牌(Tokens),然后转换成节点,最后构建成 DOM Tree

当你通过 element.style.height 获取高度时,你访问的是 DOM 节点上的属性。特性:它只能获取到写在 HTML 标签 style 属性中的值。局限性:如果高度是通过外部 CSS 文件或 .class 定义的,这里返回的是空字符串。阶段:处于 DOM 构建 阶段,尚未经过 CSSOM 的计算

2. 构建 CSSOM 树:解析所有的 CSS(包括外部文件和内联样式),构建出 CSSOM Tree。

第二步:合并成渲染树 (Render Tree)

当你调用 window.getComputedStyle(element).height 时,你访问的是 CSSOM 树 的最终计算结果。特性:无论高度来自 ID、Class 还是继承,它都会返回经过计算的像素值(例如 "200.5px")。阶段:处于 合并渲染树 (Render Tree) 之后,但在物理布局完成之前。

浏览器将 DOM 和 CSSOM 合并。

注意:display: none 的节点不会出现在渲染树中,但 visibility: hidden 的节点会。

第三步:布局 (Layout / Reflow)

计算每个节点在屏幕上的确切位置和大小。想象成在一个白纸上画方块,确定坐标。

这些 API 会触发浏览器的 强制同步布局 (Forced Synchronous Layout),即强制浏览器立即计算 Layout (Reflow) 步骤。clientHeight: 内容高度 + 内边距 (Padding)。offsetHeight: 内容高度 + 内边距 + 边框 (Border)。**getBoundingClientRect().height**: 元素在屏幕上的物理尺寸(受 transform: scale() 缩放影响后的最终视觉高度)。阶段:处于 Layout/Reflow 之后。

第四步:绘制 (Paint / Repaint)

将节点的像素信息(颜色、边框、阴影等)绘制到屏幕上。

第五步:合成 (Composite)

如果页面有复杂的层级(如 3D 转换、Canvas、video),浏览器会将它们分层处理,最后合成到一起。


3. 为什么 JS 会阻塞渲染?

原理:默认情况下,HTML 解析器遇到 <script> 标签时会暂停,去下载并执行 JS。因为 JS 可能会操作 DOM 或修改 CSS(导致前面的工作白费)

解决方案:

  • defer:脚本异步下载,等待 HTML 解析完成后执行。
  • async:脚本异步下载,下载完立即执行(可能中断解析)

4. 面试必考:重排 (Reflow) 与 重绘 (Repaint)

文章配图


5. 总结与扩展

从后端返回 HTML 开始,到屏幕显示出图像,浏览器经历了一个非常复杂的流水线。

  • SSR 提前在服务器做好了'构建对象模型'的一大部分工作。
  • CSR 把这些工作全丢给了浏览器的 JS 引擎。

三:V8 引擎

V8 引擎(Chrome 和 Node.js 的核心)之所以快,是因为它摒弃了传统的'解释执行',采用了 JIT (Just-In-Time) 即时编译技术。

核心执行流程:

  1. 解析(Parser):将源代码转为 抽象语法树(AST)。
    • 词法分析:把代码拆成一个个不可再分的词(Tokens)。
    • 语法分析:根据语法规则把 Tokens 组成树状结构。
  2. 解释(Ignition):解释器将 AST 转为字节码(Bytecode)并开始执行。
    • 字节码比机器码轻量,可以跨平台运行。
  3. 优化(TurboFan):编译器会标记'热点代码'(执行次数很多的函数),将其直接编译为高效的机器码。
  4. 去优化(Deoptimization):如果热点代码的变量类型发生了变化(例如原本传数字,后来传了字符串),V8 会把机器码退回到字节码。

面试加分点:为什么 V8 提倡写'类型确定'的代码? 因为类型一旦变化,就会触发 Deoptimization,导致性能陡降。这也是为什么 TypeScript 在大型项目中能间接提升性能(通过规范类型减少 V8 的猜疑)。


四:事件循环 (Event Loop):异步调度的灵魂

事件循环是 JS 实现非阻塞 I/O 的核心。由于 JS 是单线程的,它必须通过一个机制来协调同步代码和异步任务。

任务分类:

  • 宏任务 (Macrotask):script (整体代码), setTimeout, setInterval, I/O, UI rendering。
  • 微任务 (Microtask):Promise.then, MutationObserver, process.nextTick。

执行顺序 (Event Loop Tick)

  1. 执行一个宏任务(最开始是同步代码)。
  2. 执行过程中如果遇到微任务,放入微任务队列。
  3. 当前宏任务执行完后,立即清空所有的微任务队列。
  4. (关键点):更新渲染(Update Rendering)。
  5. 检查是否有 Web Worker 任务,开始下一个宏任务。

代码实战分析:

console.log('1'); // 同步
setTimeout(() => {
  console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
  console.log('3'); // 微任务
});
console.log('4'); // 同步
// 输出顺序:1 -> 4 -> 3 -> 2

解释:1 和 4 先入栈执行。执行完后,检查微任务队列发现 3,执行 3。最后开启下一个循环,执行宏任务 2。


五:浏览器缓存与渲染结合:Service Worker

如果说 HTTP 缓存是'自动挡',那么 Service Worker 就是'手动挡'。它是运行在浏览器后台的独立线程。Service Worker 的地位:它充当了浏览器与网络之间的代理服务器。它可以拦截网络请求,并根据策略决定是走网络、走缓存,还是直接返回一段自定义内容。

关键特性:

  1. 离线能力:即使没网,也能通过缓存加载页面(PWA 的核心)。
  2. 推送通知:可以在后台接收服务器消息。
  3. 不能直接操作 DOM:必须通过 postMessage 与主线程通信。

生命周期与缓存结合:

  • Install 阶段:通常用来预缓存静态资源(App Shell)。
  • Activate 阶段:清理旧缓存。
  • Fetch 阶段:拦截请求,实现缓存优先或网络优先策略。
// service-worker.js 示例
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // 如果缓存命中,直接返回;否则走网络请求
      return response || fetch(event.request);
    })
  );
});

总结:三者的联动

  • V8 决定了 JS 代码运行的快慢。
  • Event Loop 决定了任务执行的时机,以及是否会卡住 UI 渲染。
  • Service Worker 决定了资源加载的来源,是渲染流水线的'物料补给站'。

文章配图


六:页面加载完整流程图

文章配图


七:打包工具 webpack 的出现

1. 前端开发的痛点(Webpack 出现前)

2014 年前的前端开发环境:

  • 无模块化:所有 JS 文件通过 <script> 标签手动引入,依赖管理混乱
  • 手动合并:需要手动复制粘贴代码或使用 Grunt/Gulp 简单合并
  • 无构建优化:没有代码压缩、Tree Shaking、代码分割
  • 工具链不统一:每个项目需要不同的构建流程
  • 性能问题:
    • HTTP/1.1 的队头阻塞:每个文件一个请求
    • 无缓存策略:每次更新都要重新下载所有文件
    • 无懒加载:所有代码一次性加载

文章配图


2. 模块化开发的需求(CommonJS 的出现)

文章配图

新的问题:CommonJS 是 Node.js 的模块系统,在浏览器中无法直接运行,需要转换。


3. Webpack 的诞生(2012 年)

核心概念:模块打包器

文章配图

文章配图

文章配图

构建过程:
  1. 从入口开始:Webpack 从配置的入口文件开始
  2. 解析依赖:分析 require()、import 等语句
  3. 递归构建:对每个依赖模块重复步骤 2
  4. 生成依赖图:最终形成一个完整的模块依赖关系图

文章配图

模块加载 -Loaders 的工作原理:

文章配图

常见 Loaders:
  • babel-loader:ES6+ → ES5
  • css-loader:处理 CSS 中的 @import 和 url()
  • style-loader:将 CSS 注入到 DOM
  • file-loader:处理文件资源(图片、字体)
  • url-loader:小文件转 Base64,大文件走 file-loader
插件系统(Plugins)

Plugins 与 Loaders 的区别:

  • Loaders:在单个文件转换时工作(一对一)
  • Plugins:在整个构建过程中工作(全局)

文章配图

Webpack 配置字典
1、基础配置模块
1.1 entry(入口配置)

文章配图

详细说明:

配置类型适用场景示例说明
单入口简单应用、SPA'./src/index.js'所有代码打包到一个文件
多入口多页面应用{main: './src/index.js', admin: './src/admin.js'}每个入口生成独立的 bundle
数组入口需要前置依赖['./src/polyfills.js', './src/index.js']按顺序打包,最后合并
动态入口条件打包函数返回对象运行时确定入口,适合复杂场景

最佳实践:

  • SPA 应用:使用单入口 + 动态导入(懒加载)
  • MPA 应用:使用多入口 + 按需加载
  • 库开发:使用单入口 + libraryTarget
1.2 output(输出配置)

文章配图

详细说明:

配置项类型说明示例值
pathstring输出目录的绝对路径path.resolve(__dirname, 'dist')
filenamestring/function入口文件名模板[name].[contenthash:8].js
chunkFilenamestring非入口 chunk 文件名模板[name].[contenthash:8].chunk.js
publicPathstring资源公共路径'/' 或 'https://cdn.example.com/'
assetModuleFilenamestring静态资源文件名模板'assets/[hash][ext][query]'
cleanboolean/object是否清理输出目录true 或 { dry: true }
libraryobject库配置(打包库时使用){ name: 'MyLibrary', type: 'umd' }
globalObjectstring全局对象(UMD 模式)'this' 或 'window'

占位符说明:

占位符说明示例
[name]chunk 名称main
[id]chunk ID0
[hash]基于模块内容的哈希a1b2c3d4
[contenthash]基于 chunk 内容的哈希e5f6g7h8
[chunkhash]基于 chunk 的哈希i9j0k1l2
[ext]文件扩展名(不带点)js
[query]URL 查询字符串?v=1

2、模块解析规则(Module Rules)
2.1 JS/TS 处理

文章配图

详细说明:

配置项类型说明示例
testRegExp匹配文件扩展名`/.(js
excludeRegExp/Array排除的目录/node_modules/
includeRegExp/Array包含的目录[path.resolve(__dirname, 'src')]
useString/Object/Array使用的 Loader'babel-loader' 或 { loader: 'babel-loader', options: {...} }
parserObject解析器配置{ amd: false }
generatorObject生成器配置{ dataUrl: (content) => ... }
typeString模块类型'javascript/auto'
sideEffectsBoolean/Array副作用标记false

Loader 执行顺序:

  • pre:所有 Loader 之前执行
  • normal:普通 Loader(默认)
  • post:所有 Loader 之后执行
2.2 CSS 处理(生产环境)

文章配图

详细说明:

Loader作用生产环境配置开发环境配置
MiniCssExtractPlugin.loader提取 CSS 到文件MiniCssExtractPlugin.loaderstyle-loader
css-loader处理 @import 和 url()css-loadercss-loader
postcss-loader自动添加前缀、压缩postcss-loader + cssnanopostcss-loader + autoprefixer

CSS 模块示例:

文章配图

文章配图

2.3 SASS/SCSS 处理

文章配图

详细说明:

Loader顺序作用配置要点
style-loader/MiniCssExtractPlugin.loader1注入或提取 CSS生产环境用 MiniCssExtractPlugin
css-loader2处理 CSS 导入importLoaders 需要包含后续 loader 数量
postcss-loader3自动添加前缀配置 autoprefixer
sass-loader4编译 SASS可以添加全局变量
2.4 图片资源处理

文章配图

详细说明:

配置项类型说明示例
type: 'asset'字符串自动内联/外置'asset'
parser.dataUrlCondition对象内联条件{ maxSize: 8192 }
generator.filename字符串输出文件名'images/[name].[hash:8][ext]'
use数组额外处理 loader['image-webpack-loader']

文件大小决策逻辑:

  • < 8KB(默认):内联为 base64(减少 HTTP 请求)
  • ≥ 8KB:外置为独立文件(可以缓存)
2.5 字体文件处理

文章配图

详细说明:

配置项类型说明示例
type字符串模块类型'asset/resource'
generator.filename字符串输出文件名'fonts/[name].[hash:8][ext]'
generator.publicPath字符串公共路径'/'
2.6 JSON/YAML/XML 处理

文章配图

详细说明:

文件类型Loader配置要点
JSON内置(type: 'json')可以使用 JSON5 支持注释
YAMLyaml-loader需要安装 js-yaml
XMLxml-loader需要安装 xml2js
TOMLtoml-loader需要安装 toml

3、解析配置(Resolve)

文章配图

详细说明:

配置项类型说明示例
extensionsArray自动解析的扩展名['.js', '.jsx', '.json']
aliasObject路径别名{'@': path.resolve(__dirname, 'src')}
modulesArray模块搜索目录['node_modules', 'src']
mainFieldsArraypackage.json 字段顺序['module', 'main', 'browser']
symlinksBoolean是否解析符号链接true
unsafeCacheBoolean/RegExp缓存解析结果true 或 /node_modules/

路径别名使用示例:

// 传统方式(深层路径)
import Header from '../../../components/Header';
// 使用别名(更简洁)
import Header from '@components/Header';
// 在 CSS 中使用(需要配置 css-loader)
.container {
  background: url(@assets/images/bg.jpg);
}
4、插件配置(Plugins)
4.1 HtmlWebpackPlugin(生成 HTML)

文章配图

详细说明:

配置项类型说明示例值
templatestringHTML 模板路径'./src/index.html'
filenamestring输出文件名'index.html'
injectboolean/string资源注入位置'body'
chunksArray注入的 chunk['main', 'vendor']
minifyObjectHTML 压缩配置{ removeComments: true }
metaObjectmeta 标签配置{ description: '...' }
scriptLoadingstring脚本加载方式'defer'

模板示例:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- 其他 meta 标签 -->
  <%= htmlWebpackPlugin.tags.headTags %>
</head>
<body>
  <div></div>
  <!-- 注入的 JS -->
  <%= htmlWebpackPlugin.tags.bodyTags %>
</body>
</html>
4.2 MiniCssExtractPlugin(提取 CSS)

文章配图

详细说明:

配置项类型说明示例值
filenamestring入口 CSS 文件名'[name].[contenthash:8].css'
chunkFilenamestring非入口 chunk CSS 文件名'[name].[contenthash:8].chunk.css'
ignoreOrderboolean忽略顺序警告true
hmrboolean是否启用 HMRprocess.env.NODE_ENV === 'development'
4.3 DefinePlugin(定义全局变量)

文章配图

详细说明:

变量名说明示例值
process.env.NODE_ENVNode 环境'production'
process.env.API_BASE_URLAPI 地址'https://api.example.com'
process.env.ENABLE_ANALYTICS功能开关true
process.env.APP_VERSION应用版本'1.0.0'
DEV开发环境标志false
PROD生产环境标志true

使用示例:

// 在代码中直接使用
if (process.env.NODE_ENV === 'production') {
  // 生产环境代码
  disableDebug();
}
if (process.env.ENABLE_ANALYTICS) {
  // 启用分析
  initAnalytics();
}
4.4 CopyWebpackPlugin(复制静态文件)

文章配图

详细说明:

配置项类型说明示例
fromstring源路径'public/'
tostring目标路径'./'
toTypestring目标类型'dir'
globOptionsObjectglob 配置{ ignore: ['**/*.tmp'] }
filterfunction过滤函数(path) => !path.endsWith('.map')
transformfunction内容转换(content) => compress(content)
transformPathfunction路径转换(path) => path.replace('.js', '.min.js')
4.5 Optimization 配置(优化)
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  optimization: {
    // 1. minimize - 是否压缩
    // 作用:控制是否启用压缩
    // 生产环境通常为 true
    minimize: process.env.NODE_ENV === 'production',
    // 2. minimizer - 压缩器
    // 作用:指定使用的压缩器
    minimizer: [
      // JS 压缩器
      new TerserPlugin({
        // 并行压缩
        parallel: true,
        // 使用缓存
        cache: true,
        // 是否使用 source map
        sourceMap: process.env.NODE_ENV === 'development',
        // Terser 配置
        terserOptions: {
          // 压缩选项
          compress: {
            // 删除 console
            drop_console: true,
            // 删除 debugger
            drop_debugger: true,
            // 纯函数优化
            pure_funcs: ['console.log', 'console.info'],
            // 死代码消除
            dead_code: true,
            // 条件语句优化
            conditionals: true,
            // 优化 if-else
            if_return: true,
            // 优化 switch
            switches: true,
            // 优化循环
            loops: true,
            // 优化对象字面量
            properties: true,
            // 优化函数调用
            evaluate: true,
            // 优化算术表达式
            reduce_vars: true,
            // 优化布尔值
            booleans: true,
            // 优化数字
            numbers: true,
            // 优化字符串
            strings: true,
            // 优化正则
            regexp: true,
            // 优化模板字符串
            template_strings: true,
            // 优化箭头函数
            arrow_functions: true,
            // 优化对象方法
            methods: true,
            // 优化默认参数
            defaults: true,
            // 优化解构
            destructuring: true,
            // 优化 spread
            spread: true,
            // 优化 async/await
            async: true,
            // 优化 yield
            yield: true,
            // 优化 class
            classes: true,
            // 优化 let/const
            let_vars: true,
            // 优化 const
            const_vars: true,
            // 优化 for-of
            for_of: true,
            // 优化 for-in
            for_in: true,
            // 优化 export
            export: true,
            // 优化 import
            import: true,
            // 优化 module
            module: true,
            // 优化 strict mode
            strict: true,
            // 优化 this
            this: true,
            // 优化 arguments
            arguments: true,
            // 优化 eval
            eval: false,
            // 优化 hoist_vars
            hoist_vars: false,
            // 优化 hoist_funs
            hoist_funs: false,
            // 优化 hoist_props
            hoist_props: false,
            // 优化 hoist_func_decls
            hoist_func_decls: false,
            // 优化 pure_getters
            pure_getters: 'strict',
            // 优化 keep_fargs
            keep_fargs: false,
            // 优化 keep_fnames
            keep_fnames: false,
            // 优化 keep_classnames
            keep_classnames: false,
            // 优化 keep_infinity
            keep_infinity: false,
            // 优化 side_effects
            side_effects: true,
            // 优化 unused
            unused: true,
            // 优化 warnings
            warnings: false
          },
          // 混淆选项
          mangle: {
            // 保留类名
            keep_classnames: false,
            // 保留函数名
            keep_fnames: false,
            // Safari 10 兼容
            safari10: true,
            // 属性名混淆
            properties: {
              // 保留的属性名
              reserved: ['__esModule', 'default'],
              // 是否混淆属性名
              regex: null,
              // 是否使用 debug 属性
              debug: false,
              // 是否使用保留字
              reserved_names: null
            }
          },
          // 输出选项
          output: {
            // 保留注释
            comments: false,
            // 保留版权注释
            preserve_line: false,
            // 美化输出
            beautify: false,
            // ASCII 限制
            ascii_only: false,
            // 引号样式
            quote_style: 0,
            // 最大行长度
            max_line_len: 32000,
            // 保留行
            preserve_line: false,
            // 保留语句结束
            semicolons: true,
            // 保留缩进
            indent_level: 3,
            // 保留缩进
            indent_start: 0,
            // 保留斜杠
            keep_quoted_props: false,
            // 是否使用括号
            wrap_iife: false,
            // 是否使用括号包裹函数调用
            wrap_func_args: false,
            // 是否使用括号包裹对象
            wrap_object: false,
            // 是否使用括号包裹数组
            wrap_array: false,
            // 是否使用括号包裹函数表达式
            wrap_function_expressions: false,
            // 是否使用括号包裹箭头函数
            wrap_arrow_functions: false,
            // 是否使用括号包裹类
            wrap_classes: false,
            // 是否使用括号包裹条件
            wrap_conditions: false,
            // 是否使用括号包裹循环
            wrap_loops: false,
            // 是否使用括号包裹 switch
            wrap_switches: false,
            // 是否使用括号包裹 try-catch
            wrap_try_catch: false,
            // 是否使用括号包裹对象字面量
            wrap_object_literals: false,
            // 是否使用括号包裹数组字面量
            wrap_array_literals: false,
            // 是否使用括号包裹函数字面量
            wrap_function_literals: false,
            // 是否使用括号包裹类字面量
            wrap_class_literals: false,
            // 是否使用括号包裹条件字面量
            wrap_condition_literals: false,
            // 是否使用括号包裹循环字面量
            wrap_loop_literals: false,
            // 是否使用括号包裹 switch 字面量
            wrap_switch_literal_expressions: false,
            // 是否使用括号包裹 try-catch 字面量
            wrap_try_catch_literal_expressions: false
          },
          // 源码选项
          sourceMap: {
            // 是否包含源码
            includeSources: false,
            // 是否使用 source map
            file: null,
            // 是否使用 source map
            url: null
          }
        }
      }),
      // CSS 压缩器
      new CssMinimizerPlugin({
        // 并行压缩
        parallel: true,
        // 使用缓存
        cache: true,
        // 是否使用 source map
        sourceMap: process.env.NODE_ENV === 'development',
        // cssnano 配置
        minimizerOptions: {
          preset: [
            'default',
            {
              // 优化选项
              discardComments: {
                removeAll: true
              },
              // 移除空规则
              discardEmpty: true,
              // 合并规则
              mergeLonghand: true,
              // 合并媒体查询
              mergeRules: true,
              // 优化 calc()
              calc: {
                precision: 5
              },
              // 优化颜色
              colormin: true,
              // 优化转换
              reduceTransforms: true,
              // 优化 svgo
              svgo: false
            }
          ]
        }
      })
    ],
    // 3. splitChunks - 代码分割
    // 作用:提取公共代码
    splitChunks: {
      // 4. chunks - 适用的 chunk 类型
      // 选项:'initial' | 'async' | 'all'
      // 'initial': 入口 chunk
      // 'async': 异步 chunk(动态导入)
      // 'all': 所有 chunk
      chunks: 'all',
      // 5. minSize - 最小大小
      // 作用:chunk 的最小大小(字节)
      // 小于此大小的 chunk 不会被分割
      minSize: 30000, // 30KB
      // 6. maxSize - 最大大小
      // 作用:chunk 的最大大小(字节)
      // 超过此大小的 chunk 会被分割
      maxSize: 250000, // 250KB
      // 7. minChunks - 最小引用次数
      // 作用:模块被引用的最小次数
      // 才能被提取到公共 chunk
      minChunks: 2,
      // 8. maxAsyncRequests - 最大异步请求数
      // 作用:按需加载时最大的并行请求数
      maxAsyncRequests: 5,
      // 9. maxInitialRequests - 最大初始请求数
      // 作用:入口 chunk 最大并行请求数
      maxInitialRequests: 3,
      // 10. automaticNameDelimiter - 自动名称分隔符
      // 作用:自动生成 chunk 名称的分隔符
      automaticNameDelimiter: '~',
      // 11. name - chunk 名称
      // 作用:自定义 chunk 名称
      // 函数:(module, chunks, cacheGroupKey) => string
      name: (module, chunks, cacheGroupKey) => {
        return cacheGroupKey;
      },
      // 12. cacheGroups - 缓存组
      // 作用:定义分割规则
      cacheGroups: {
        // 13. vendors - 第三方库
        vendors: {
          // 测试:匹配 node_modules
          test: /[\\/]node_modules[\\/]/,
          // 优先级:数值越小优先级越高
          priority: -10,
          // chunk 名称
          name: 'vendors',
          // 是否复用已存在的 chunk
          reuseExistingChunk: true,
          // 是否强制分割
          enforce: true
        },
        // 14. commons - 公共代码
        commons: {
          // 测试:匹配 src 目录下的公共代码
          test: /[\\/]src[\\/]common[\\/]/,
          // 最小引用次数
          minChunks: 2,
          // 优先级
          priority: -20,
          // 名称
          name: 'commons',
          // 复用
          reuseExistingChunk: true
        },
        // 15. react - React 相关
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          priority: 30,
          reuseExistingChunk: true
        },
        // 16. styles - 样式文件
        styles: {
          // 测试:匹配 CSS 文件
          test: /\.css$/,
          // chunk 类型:所有
          chunks: 'all',
          // 强制分割
          enforce: true,
          // 名称
          name: 'styles',
          // 优先级
          priority: -5,
          // 复用
          reuseExistingChunk: true
        },
        // 17. dynamic - 动态导入
        dynamic: {
          // 测试:匹配动态导入
          test: /[\\/]src[\\/]pages[\\/]/,
          // 只对异步 chunk 生效
          chunks: 'async',
          // 名称
          name: (module, chunks, cacheGroupKey) => {
            return `${cacheGroupKey}-${chunks[0].name}`;
          },
          // 优先级
          priority: 10,
          // 复用
          reuseExistingChunk: true
        }
      }
    },
    // 18. runtimeChunk - 运行时代码
    // 作用:提取 Webpack 运行时代码
    // 选项:boolean | string | object
    // true: 提取为单独文件
    // 'single': 提取为单个文件
    // 'multiple': 每个入口提取一个
    // object: 自定义配置
    runtimeChunk: {
      name: 'runtime'
    },
    // 19. moduleIds - 模块 ID 生成策略
    // 作用:控制模块 ID 的生成方式
    // 选项:'named' | 'deterministic' | 'hashed' | 'size' | 'total-size'
    // 'named': 使用模块路径(开发环境)
    // 'deterministic': 短数字 ID(生产环境,长期缓存)
    moduleIds: process.env.NODE_ENV === 'production' ? 'deterministic' : 'named',
    // 20. chunkIds - chunk ID 生成策略
    // 作用:控制 chunk ID 的生成方式
    // 选项:'named' | 'deterministic' | 'natural'
    chunkIds: process.env.NODE_ENV === 'production' ? 'deterministic' : 'named',
    // 21. nodeEnv - 设置 process.env.NODE_ENV
    // 作用:设置 process.env.NODE_ENV 的值
    nodeEnv: process.env.NODE_ENV || 'development',
    // 22. providedExports - 导出分析
    // 作用:分析模块的导出
    // 用于 Tree Shaking
    providedExports: true,
    // 23. usedExports - 使用分析
    // 作用:分析哪些导出被使用
    // 用于 Tree Shaking
    usedExports: true,
    // 24. sideEffects - 副作用分析
    // 作用:分析模块的副作用
    // 用于 Tree Shaking
    sideEffects: true,
    // 25. concatenateModules - 模块合并
    // 作用:合并多个模块为一个
    // 用于优化包大小
    concatenateModules: process.env.NODE_ENV === 'production',
    // 26. mangleExports - 导出混淆
    // 作用:混淆导出名称
    // 用于优化包大小
    mangleExports: process.env.NODE_ENV === 'production',
    // 27. portableRecords - 便携记录
    // 作用:生成便携的记录文件
    // 用于持久化缓存
    portableRecords: true,
    // 28. realContentHash - 真实内容哈希
    // 作用:使用真实内容哈希
    // 用于长期缓存
    realContentHash: true,
    // 29. emitOnErrors - 错误时是否输出
    // 作用:即使有错误是否输出文件
    // 用于开发环境
    emitOnErrors: false,
    // 30. checkWasmTypes - 检查 WASM 类型
    // 作用:检查 WASM 模块类型
    // 用于 WASM 模块
    checkWasmTypes: true,
    // 31. minimize - 是否最小化
    // 作用:覆盖 optimization.minimize
    // 优先级更高
    minimize: true,
    // 32. removeAvailableModules - 移除可用模块
    // 作用:移除重复模块
    removeAvailableModules: true,
    // 33. removeEmptyChunks - 移除空 chunk
    // 作用:移除不包含模块的 chunk
    removeEmptyChunks: true,
    // 34. mergeDuplicateChunks - 合并重复 chunk
    // 作用:合并内容相同的 chunk
    mergeDuplicateChunks: true
  }
};

详细说明:

配置项类型说明示例
minimizeboolean是否压缩process.env.NODE_ENV === 'production'
minimizerArray压缩器配置[new TerserPlugin(), new CssMinimizerPlugin()]
splitChunksObject代码分割配置{ chunks: 'all', cacheGroups: {...} }
runtimeChunkboolean/string运行时代码提取'single'
moduleIdsstring模块 ID 策略'deterministic'
usedExportsboolean使用分析(Tree Shaking)true
sideEffectsboolean副作用分析(Tree Shaking)true
5、开发服务器配置(DevServer)

文章配图

详细说明:

配置项类型说明示例
staticObject静态文件配置{ directory: path.join(__dirname, 'public') }
hoststring主机地址'localhost'
portnumber/string端口号3000
openboolean/string自动打开浏览器true
hotboolean热模块替换true
compressbooleanGzip 压缩true
historyApiFallbackboolean/ObjectSPA 路由支持true
proxyObject代理配置{ '/api': { target: '...' } }
headersObject响应头{ 'X-Custom-Header': 'value' }
clientObject客户端配置{ overlay: { errors: true } }

代理配置示例:

proxy: {
  // 简单代理
  '/api': 'http://localhost:8080',
  // 完整配置
  '/api': {
    target: 'http://localhost:8080',
    changeOrigin: true, // 更改请求头中的 host
    pathRewrite: {
      '^/api': '' // 重写路径
    },
    secure: false, // HTTPS 证书验证
    ws: true, // WebSocket 代理
    bypass: (req, res) => {
      // 特定条件不代理
      if (req.headers.accept.includes('html')) {
        return '/index.html';
      }
    }
  }
}
6、调试与监控配置
6.1 Source Maps 配置

文章配图

devtool 模式说明:

模式速度质量适用场景
eval极快差开发环境
eval-source-map快较好开发环境
cheap-module-eval-source-map快中等开发环境
source-map慢最好生产环境(需要调试)
hidden-source-map慢最好生产环境(调试,但不显示)
nosources-source-map慢中等生产环境(不暴露源码)
6.2 性能监控配置

文章配图

7、不同场景的配置示例

文章配图

库开发注意事项:

  1. 使用 UMD 格式:兼容 CommonJS、AMD 和全局变量
  2. 外部依赖:将 React 等作为外部依赖,避免重复打包
  3. 压缩代码:生产环境压缩,减少库体积
  4. 类型声明:使用 TypeScript 或生成 .d.ts 文件
  5. Tree Shaking:确保代码无副作用,支持 Tree Shaking
8、Webpack 5 新特性配置
8.1 资源模块(Asset Modules)

文章配图

8.2 持久化缓存

文章配图

8.3 未来兼容性配置

文章配图

9、性能优化最佳实践
9.1 构建性能优化

文章配图

9.2 运行时性能优化

文章配图

9.3 长期缓存优化

文章配图

长期缓存策略:

  1. 文件名哈希:使用 [contenthash] 确保内容变化时文件名变化
  2. 分离公共代码:提取第三方库,长期缓存
  3. 运行时代码分离:避免业务代码变化影响缓存
  4. CDN 部署:使用 CDN 加速静态资源加载
  5. 缓存控制:设置合适的 Cache-Control 头
10、调试与问题排查
10.1 常见问题配置

文章配图

10.2 环境变量配置

文章配图


八:Vite

基于我们之前的 Webpack 讨论,现在深入探讨 Vite。前端构建工具的范式转变,从'打包'转向'按需编译'

1、Vite 的诞生背景与核心理念

1.1 Webpack 的瓶颈

Webpack 在开发环境中的问题:

// Webpack 的开发流程
1. 打包整个应用 → 生成 bundle.js
2. 启动本地服务器
3. 每次修改代码 → 重新打包 → 热更新
4. 项目越大,打包时间越长

性能数据对比:

  • 小型项目(< 100 个模块):Webpack 启动时间约 3-5 秒
  • 中型项目(100-500 个模块):Webpack 启动时间约 5-15 秒
  • 大型项目(> 500 个模块):Webpack 启动时间可能超过 30 秒
1.2 Vite 的核心理念

'按需编译' vs '预先打包':

Webpack:预先打包
所有模块先打包成 bundle.js,浏览器下载并执行
Vite:按需编译
浏览器请求时,Vite 动态编译模块并返回
无需等待整个应用打包完成

关键优势:

  1. 冷启动极快:无论项目多大,启动时间几乎恒定
  2. 热更新极快:毫秒级 HMR
  3. 开箱即用:零配置或极简配置
  4. 生产优化:使用 Rollup 进行生产构建

2、Vite 的工作原理

2.1 开发环境:ESM 驱动的开发服务器

传统 Webpack 开发流程:

浏览器请求 → Webpack 打包 → 生成 bundle → 返回 HTML → 执行 JS
↓
每次修改都重复

Vite 开发流程:

浏览器请求 → Vite 按需编译 → 动态返回 ESM 模块
↓
只编译当前需要的模块

详细流程:

  1. 服务器启动:Vite 启动 ESM 开发服务器,不进行打包
  2. 依赖预构建:将 CommonJS/UMD 依赖转换为 ESM 并缓存
  3. 按需编译:当浏览器请求 /src/main.js 时:
    • 解析导入语句
    • 转换 TypeScript/JSX 等
    • 返回 ESM 模块
  4. 动态导入:浏览器继续请求依赖的模块,Vite 动态编译
2.2 依赖预构建
// node_modules 中的依赖通常是 CommonJS 或 UMD 格式
// Vite 需要将它们转换为 ESM
// 转换前(CommonJS)
module.exports = {
  default: 'value'
};
// 转换后(ESM)
export default {
  default: 'value'
};

预构建的好处:

  1. 性能:避免每次请求都转换
  2. 兼容性:确保依赖能正确在浏览器中运行
  3. 缓存:转换结果缓存在 node_modules/.vite 目录
2.3 生产构建:Rollup 优化

为什么使用 Rollup?

  1. Tree Shaking:Rollup 的 Tree Shaking 更彻底
  2. 代码分割:Rollup 的代码分割算法更优秀
  3. 模块格式:Rollup 支持多种输出格式
  4. 插件系统:Rollup 插件更专注,性能更好

3、Vite 配置详解

3.1 基础配置
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import vue from '@vitejs/plugin-vue';
import legacy from '@vitejs/plugin-legacy';

export default defineConfig({
  // 1. 基础路径
  // 作用:部署路径
  // 示例:部署到 GitHub Pages 时设置为'/repository-name/'
  base: '/',
  // 2. 开发服务器配置
  server: {
    // 主机地址
    host: 'localhost',
    // 端口
    port: 3000,
    // 是否自动打开浏览器
    open: true,
    // HTTPS 配置
    https: false,
    // 热更新配置
    hmr: {
      overlay: true, // 错误覆盖层
      clientPort: 3000 // 客户端端口
    },
    // 代理配置
    proxy: {
      // 基础代理
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
      // WebSocket 代理
      '/ws': {
        target: 'ws://localhost:8080',
        ws: true,
        changeOrigin: true
      }
    },
    // 3. 预览服务器配置
    preview: {
      port: 4173,
      open: true
    }
  },
  // 4. 构建配置
  build: {
    // 输出目录
    outDir: 'dist',
    // 静态资源目录
    assetsDir: 'assets',
    // 是否生成 source map
    sourcemap: false,
    // 是否压缩
    minify: 'terser', // 'terser' | 'esbuild' | false
    // 代码分割配置
    rollupOptions: {
      // 入口配置
      input: {
        main: './index.html',
        // 多页面应用
        // admin: './admin.html'
      },
      // 输出配置
      output: {
        // 手动代码分割
        manualChunks: (id) => {
          // 将 node_modules 中的依赖分割到 vendor
          if (id.includes('node_modules')) {
            return 'vendor';
          }
          // 将特定模块分割
          if (id.includes('src/pages')) {
            return 'pages';
          }
        },
        // 资源文件命名
        entryFileNames: '[name].[hash].js',
        chunkFileNames: '[name].[hash].js',
        assetFileNames: '[name].[hash].[ext]'
      }
    },
    // 大小警告阈值
    chunkSizeWarningLimit: 500, // 500KB
    // 是否报告分析
    reportCompressedSize: true
  },
  // 5. CSS 配置
  css: {
    // 预处理器配置
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      },
      less: {
        modifyVars: {
          'primary-color': '#1DA57A'
        }
      }
    },
    // 是否提取 CSS 到独立文件
    extract: true,
    // 是否启用 CSS 模块
    modules: {
      // 生成的类名格式
      generateScopedName: '[name]__[local]--[hash:base64:5]',
      // 配置文件
      localsConvention: 'camelCaseOnly'
    },
    // 是否启用 source map
    devSourcemap: true
  },
  // 6. 解析配置
  resolve: {
    // 路径别名
    alias: {
      '@': '/src',
      '@components': '/src/components',
      '@utils': '/src/utils',
      '@assets': '/src/assets',
      // 解决模块重复问题
      'react': '/node_modules/react/index.js',
      'react-dom': '/node_modules/react-dom/index.js'
    },
    // 扩展名
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.vue'],
    // 条件导出
    conditions: ['import', 'require', 'node', 'default'],
    // 主字段
    mainFields: ['module', 'main', 'browser']
  },
  // 7. 环境变量配置
  envPrefix: 'VITE_',
  // 8. 定义全局变量
  define: {
    '__APP_VERSION__': JSON.stringify(process.env.npm_package_version),
    '__BUILD_TIME__': Date.now()
  },
  // 9. 插件配置
  plugins: [
    // React 支持
    react({
      // Babel 配置
      babel: {
        plugins: [
          ['@babel/plugin-proposal-decorators', { legacy: true }],
          ['@babel/plugin-proposal-class-properties', { loose: true }]
        ]
      },
      // 是否启用 Fast Refresh
      fastRefresh: true,
      // 是否启用 React 17 JSX 转换
      jsxRuntime: 'automatic'
    }),
    // Vue 支持(如果使用 Vue)
    vue({
      // Vue 配置
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith('ion-')
        }
      }
    }),
    // 旧版浏览器支持
    legacy({
      targets: ['defaults', 'not IE 11'],
      polyfills: [
        'es.promise.finally',
        'es.map',
        'es.set',
        'es.array.find-index',
        'es.promise',
        'es.object.assign'
      ],
      modernPolyfills: ['web.dom.iterable']
    }),
    // SVG 作为 React 组件
    // 需要安装:vite-plugin-svgr
    // import svgr from 'vite-plugin-svgr';
    // svgr()
  ],
  // 10. 测试配置
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './tests/setup.js',
    coverage: {
      reporter: ['text', 'json', 'html'],
      exclude: ['node_modules/', 'tests/']
    }
  }
});
3.2 环境变量管理
# .env
VITE_API_URL=https://api.example.com
VITE_APP_NAME=My Application
VITE_ENABLE_ANALYTICS=true

# .env.development
VITE_API_URL=http://localhost:8080
VITE_DEBUG=true

# .env.production
VITE_API_URL=https://api.example.com
VITE_DEBUG=false

在代码中使用:

// 访问环境变量
const apiUrl = import.meta.env.VITE_API_URL;
const debug = import.meta.env.VITE_DEBUG === 'true';

// 类型提示(创建 src/vite-env.d.ts)
/// <reference types="vite/client" />
interface ImportMetaEnv {
  readonly VITE_API_URL: string;
  readonly VITE_APP_NAME: string;
  readonly VITE_ENABLE_ANALYTICS: boolean;
}
interface ImportMeta {
  readonly env: ImportMetaEnv;
}
3.3 多页面应用配置
// vite.config.js
import { resolve } from 'path';
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        admin: resolve(__dirname, 'admin.html'),
        about: resolve(__dirname, 'about.html'),
        contact: resolve(__dirname, 'contact.html')
      }
    }
  },
  plugins: [
    // 多页面 HTML 插件
    {
      name: 'multi-page-plugin',
      transformIndexHtml(html) {
        return html;
      }
    }
  ]
});
3.4 库开发配置
// vite.config.js (库开发)
import { defineConfig } from 'vite';
import { resolve } from 'path';

export default defineConfig({
  build: {
    // 输出格式
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'MyLibrary',
      fileName: (format) => `my-library.${format}.js`
    },
    // 外部依赖
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM'
        }
      }
    }
  },
  // 不需要插件(除非需要特定转换)
  plugins: []
});

4、Vite vs Webpack:详细对比

4.1 开发体验对比
特性WebpackVite优势方
冷启动时间随项目增大而增长几乎恒定(<1 秒)Vite
热更新速度1-5 秒<100msVite
配置复杂度高(需要大量配置)低(开箱即用)Vite
TypeScript 支持需要配置 loader原生支持Vite
CSS 处理需要配置 loader原生支持Vite
代码分割手动配置自动优化Vite
Tree Shaking需要配置Rollup 原生支持Vite
4.2 生产构建对比
特性WebpackVite (Rollup)
Tree Shaking支持,但需要配置支持,更彻底
代码分割支持,配置复杂支持,自动优化
输出格式多种多种,更灵活
构建速度较慢较快(Rollup 效率高)
包大小较大较小(Tree Shaking 更优)

5、Vite 高级配置

5.1 自定义插件开发
// vite.config.js
import { defineConfig } from 'vite';

// 自定义插件示例
function myPlugin() {
  return {
    name: 'my-custom-plugin',
    // 转换钩子
    transform(code, id) {
      if (id.endsWith('.js')) {
        // 在 JS 文件末尾添加注释
        return {
          code: code + '\n// My custom plugin added this',
          map: null
        };
      }
    },
    // HTML 转换钩子
    transformIndexHtml(html) {
      return html.replace(
        '</body>',
        '<script>console.log("Plugin added script")</script></body>'
      );
    },
    // 服务器中间件
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        if (req.url === '/custom') {
          res.end('Custom response from plugin');
        } else {
          next();
        }
      });
    }
  };
}

export default defineConfig({
  plugins: [myPlugin()]
});
5.2 CSS Modules 增强配置
// vite.config.js
export default defineConfig({
  css: {
    modules: {
      // 生成的类名格式
      generateScopedName: '[name]__[local]--[hash:base64:5]',
      // 导出命名约定
      localsConvention: 'camelCaseOnly',
      // 配置文件路径
      hashPrefix: 'my-project'
    },
    // 预处理器
    preprocessorOptions: {
      scss: {
        additionalData: `
          @import "@/styles/variables.scss";
          @import "@/styles/mixins.scss";
        `,
        // 全局 SCSS 变量
        javascriptEnabled: true
      }
    }
  }
});
5.3 性能优化配置
// vite.config.js
export default defineConfig({
  build: {
    // 代码分割策略
    rollupOptions: {
      output: {
        // 手动代码分割
        manualChunks: (id) => {
          // 分割 node_modules
          if (id.includes('node_modules')) {
            // 按包名分割
            const packageName = id.match(/node_modules\/([^\/]+)/)?.[1];
            if (packageName) {
              return `vendor-${packageName}`;
            }
            return 'vendor';
          }
          // 分割业务模块
          if (id.includes('src/pages')) {
            const pageName = id.match(/src\/pages\/([^\/]+)/)?.[1];
            if (pageName) {
              return `page-${pageName}`;
            }
          }
          if (id.includes('src/components')) {
            return 'components';
          }
          if (id.includes('src/utils')) {
            return 'utils';
          }
        },
        // 资源文件命名(长期缓存)
        entryFileNames: '[name].[hash].js',
        chunkFileNames: '[name].[hash].js',
        assetFileNames: (info) => {
          const ext = info.name.split('.').pop();
          if (ext === 'css') {
            return `[name].[hash].css`;
          }
          if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext)) {
            return `images/[name].[hash].[ext]`;
          }
          if (['woff', 'woff2', 'eot', 'ttf', 'otf'].includes(ext)) {
            return `fonts/[name].[hash].[ext]`;
          }
          return `[name].[hash].[ext]`;
        }
      }
    },
    // 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
        pure_funcs: ['console.log', 'console.info']
      }
    },
    // CSS 代码分割
    cssCodeSplit: true,
    // 资源内联阈值
    assetsInlineLimit: 4096, // 4KB 以下内联
    // 是否生成 Gzip 压缩
    reportCompressedSize: true
  },
  // 性能监控
  logLevel: 'info',
  clearScreen: false
});

6、Vite 的局限性与挑战

6.1 不适合的场景
  1. 大量 CommonJS 依赖的项目
    • Vite 需要转换 CommonJS 到 ESM,可能有性能开销
    • 解决方案:使用 optimizeDeps 预构建
  2. 需要复杂自定义配置的项目
    • Vite 配置相对简单,但灵活性可能不足
    • 解决方案:编写自定义插件
  3. 需要特定 Webpack 插件的项目
    • 某些 Webpack 插件可能没有 Vite 等效版本
    • 解决方案:寻找替代方案或自定义插件
  4. 旧版浏览器支持
    • Vite 默认目标是现代浏览器
    • 解决方案:使用 @vitejs/plugin-legacy
6.2 性能挑战
  1. 依赖预构建
    • 首次启动需要预构建依赖,可能耗时
    • 解决方案:使用 optimizeDeps 缓存
  2. 大型项目热更新
    • 虽然比 Webpack 快,但超大项目仍可能有延迟
    • 解决方案:代码分割、懒加载
  3. 内存使用
    • 开发服务器可能占用较多内存
    • 解决方案:监控和优化
6.3 生态成熟度
  1. 插件数量
    • Vite 插件生态快速增长,但仍少于 Webpack
    • 解决方案:使用 Rollup 插件(大部分兼容)
  2. 企业级功能
    • 某些企业级功能可能需要额外工作
    • 解决方案:组合使用多个工具

目录

  1. 一:从输入 URL 到页面渲染
  2. 1. URL 的标准组成部分
  3. URI 和 URL 的区别?
  4. 1. 什么是“同源”?
  5. 2. 常见的跨域解决方案
  6. A. CORS (Cross-Origin Resource Sharing) - 最主流
  7. B. JSONP (JSON with Padding) - 兼容老旧浏览器
  8. C. Proxy (代理) - 开发环境最常用
  9. 3. 面试官深挖:Cookie 如何在跨域中携带?
  10. 总结建议
  11. 2. DNS 解析过程
  12. 1. DNS 解析过程:将域名变为 IP 地址
  13. 3. TCP/IP 分层管理:数据是如何打包的?
  14. 1. 应用层:
  15. 2. 传输层:
  16. 3. 网络层 (又名网络互连层)
  17. 4. 链路层 (又名数据链路层,网络接口层)
  18. 图解
  19. 4. TCP 三次握手:确保双方都有收发能力
  20. 5. 四次挥手的详细过程
  21. 面试高频追问:为什么会有 TIME_WAIT?
  22. 代码与性能层面:CLOSE_WAIT 过多怎么办?
  23. 6. HTTP 缓存策略
  24. 1. 缓存的整体流程
  25. 2. 强缓存 (Strong Cache)
  26. 关键响应头:
  27. 3. 协商缓存 (Negotiation Cache)
  28. 4. 模拟代码场景:如何设置缓存?
  29. 5. 面试官杀手锏:用户操作对缓存的影响
  30. 总结建议
  31. 7. 状态码
  32. 1. 状态码分类概览
  33. 2xx - 成功
  34. 3xx - 重定向
  35. 4xx - 客户端错误
  36. 5xx - 服务器错误
  37. 2. 面试官进阶:301 和 302 对 SEO 的影响?
  38. 总结建议
  39. 8. HTTP 和 HTTPS 的区别
  40. 1. 核心区别汇总
  41. 2. HTTPS 的“安全”是如何实现的?
  42. 3. HTTPS 的握手过程(核心考点)
  43. 4. 代码层面:前端需要做什么?
  44. 5. 面试官可能追问:什么是中间人攻击(MITM)?
  45. 二:浏览器渲染原理
  46. 1. 渲染模式的分水岭:后端返回了什么?
  47. 1. 服务端渲染 (SSR - Server Side Rendering)
  48. 2. 客户端渲染 (CSR - Client Side Rendering)
  49. 2. 浏览器渲染的详细流水线 (The Critical Rendering Path)
  50. 第一步:构建对象模型
  51. 第二步:合并成渲染树 (Render Tree)
  52. 第三步:布局 (Layout / Reflow)
  53. 第四步:绘制 (Paint / Repaint)
  54. 第五步:合成 (Composite)
  55. 3. 为什么 JS 会阻塞渲染?
  56. 4. 面试必考:重排 (Reflow) 与 重绘 (Repaint)
  57. 5. 总结与扩展
  58. 三:V8 引擎
  59. 四:事件循环 (Event Loop):异步调度的灵魂
  60. 五:浏览器缓存与渲染结合:Service Worker
  61. 六:页面加载完整流程图
  62. 七:打包工具 webpack 的出现
  63. 1. 前端开发的痛点(Webpack 出现前)
  64. 2. 模块化开发的需求(CommonJS 的出现)
  65. 3. Webpack 的诞生(2012 年)
  66. 构建过程:
  67. 模块加载 -Loaders 的工作原理:
  68. 常见 Loaders:
  69. 插件系统(Plugins)
  70. Webpack 配置字典
  71. 1、基础配置模块
  72. 1.1 entry(入口配置)
  73. 1.2 output(输出配置)
  74. 2、模块解析规则(Module Rules)
  75. 2.1 JS/TS 处理
  76. 2.2 CSS 处理(生产环境)
  77. 2.3 SASS/SCSS 处理
  78. 2.4 图片资源处理
  79. 2.5 字体文件处理
  80. 2.6 JSON/YAML/XML 处理
  81. 3、解析配置(Resolve)
  82. 4、插件配置(Plugins)
  83. 4.1 HtmlWebpackPlugin(生成 HTML)
  84. 4.2 MiniCssExtractPlugin(提取 CSS)
  85. 4.3 DefinePlugin(定义全局变量)
  86. 4.4 CopyWebpackPlugin(复制静态文件)
  87. 4.5 Optimization 配置(优化)
  88. 5、开发服务器配置(DevServer)
  89. 6、调试与监控配置
  90. 6.1 Source Maps 配置
  91. 6.2 性能监控配置
  92. 7、不同场景的配置示例
  93. 8、Webpack 5 新特性配置
  94. 8.1 资源模块(Asset Modules)
  95. 8.2 持久化缓存
  96. 8.3 未来兼容性配置
  97. 9、性能优化最佳实践
  98. 9.1 构建性能优化
  99. 9.2 运行时性能优化
  100. 9.3 长期缓存优化
  101. 10、调试与问题排查
  102. 10.1 常见问题配置
  103. 10.2 环境变量配置
  104. 八:Vite
  105. 1、Vite 的诞生背景与核心理念
  106. 1.1 Webpack 的瓶颈
  107. 1.2 Vite 的核心理念
  108. 2、Vite 的工作原理
  109. 2.1 开发环境:ESM 驱动的开发服务器
  110. 2.2 依赖预构建
  111. 2.3 生产构建:Rollup 优化
  112. 3、Vite 配置详解
  113. 3.1 基础配置
  114. 3.2 环境变量管理
  115. .env
  116. .env.development
  117. .env.production
  118. 3.3 多页面应用配置
  119. 3.4 库开发配置
  120. 4、Vite vs Webpack:详细对比
  121. 4.1 开发体验对比
  122. 4.2 生产构建对比
  123. 5、Vite 高级配置
  124. 5.1 自定义插件开发
  125. 5.2 CSS Modules 增强配置
  126. 5.3 性能优化配置
  127. 6、Vite 的局限性与挑战
  128. 6.1 不适合的场景
  129. 6.2 性能挑战
  130. 6.3 生态成熟度
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 国内升级 GitHub Copilot 专业版支付方案
  • 前端数据库 IndexedDB 详解:构建离线 Web 应用
  • 国内如何付费升级 GitHub Copilot 专业版
  • 国内如何升级 GitHub Copilot 到专业版
  • 基于百度天气与 WebGIS 构建复古风格天气预报系统
  • Obsidian 笔记同步至 Gitee 云存储指南
  • Java 集成高德开放平台 WebAPI 实现 POI 搜索实践
  • 前端 SSG 详解:静态站点生成最佳实践
  • Java 数据结构:HashMap 与 TreeMap 区别及 Map 与 Set 关系
  • Python 零基础入门教程:基础语法与核心概念详解
  • Java Web 开发入门:基础概念、环境搭建与 Servlet/JSP
  • 深入理解 ES6 核心语法:进制、Symbol 与类继承
  • Office 区域不支持 Copilot 的解决方案
  • Gitee 仓库创建与本地项目上传指南
  • 从 Webhook 到 OpenClaw:钉钉周报提醒机器人的技术演进
  • LeetCode 热题 100 Python 算法题解:哈希、双指针、滑动窗口及子串
  • Java 实现 MCP 服务:构建 LLM 专属工具库基座
  • Git 在 Linux 及麒麟国防版 V10 上的安装配置
  • RRT 算法详细介绍(Python)
  • GESP C++ 一级考试全流程及编程题核心模板

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online