1. 前端特殊字符:不是“乱码”,是编码世界的通行证
你有没有遇到过这样的场景:在 Vue 组件里写<span>价格:¥99</span>,上线后变成价格:Â¥99;或者从后端接口拿到一段带中文引号的文案“欢迎来到首页”,渲染出来却是“欢迎æ¥å°é¦–页â€;又或者在 Edge 浏览器里打开 PDF 文件,里面明明是©2024 公司名称,却显示成©2024 å…¬å¸åç§°。这些不是 bug,也不是前端写错了,而是你和字符集打了个照面,但没认出对方——它穿着 UTF-8 的衣服,你却用 ISO-8859-1 的眼睛在看。
“前端常用的特殊字符集”这个标题,表面看是讲几个符号怎么打、怎么转义,实则是一条贯穿 HTML 渲染、JavaScript 字符处理、HTTP 传输、字体回退、甚至 PDF 嵌入的底层链路。它不单是©和©的选择题,更是前端工程师对“文本如何被计算机理解”这件事的实操认知边界。我做过 7 个大型政企级 Web 系统的前端架构,其中 3 个因字符集配置疏漏,在多语言切换、PDF 导出、Excel 下载环节翻过车——不是功能不能用,而是用户看到的“文字”变成了“密码”。这类问题往往在测试环境不暴露,一上生产就集中在海外用户、财务系统、合同生成等关键路径爆发,排查起来像在迷宫里找出口:浏览器控制台没报错、Network 面板看响应体是“正常”的、后端日志也干净,最后发现根源在<meta charset="utf-8">这行代码被某次模板合并时悄悄删掉了。
所以这篇内容不是字符表速查手册,而是一份“前端字符生存指南”。它覆盖你每天真实接触的 5 类场景:HTML 中的实体字符(如 )、CSS 里的 Unicode 转义(如\00a9)、JavaScript 字符串的编码处理(encodeURIComponentvsencodeURI)、富文本编辑器中的不可见字符(零宽空格、软连字符)、以及最常被忽视的“字体层”——为什么你的€符号在 Windows 上显示正常,在 macOS 上却变成方块?我会拆解每个字符背后的真实字节构成,告诉你 Chrome DevTools 里 Network 标签页的“Response Headers”中Content-Type: text/html; charset=utf-8这串字符到底在指挥什么,也会手把手带你用file命令和iconv工具验证一个 JS 文件的真实编码,而不是靠编辑器右下角那个可能被欺骗的“UTF-8”标识。如果你正被面试官问到“为什么decodeURIComponent('%C2%A9')返回©而不是©”,或者正在调试一个从 Oracle 数据库导出 CSV 后中文全变问号的报表系统,那接下来的内容,就是你缺的那一块拼图。
2. 字符集本质:从字节到屏幕的完整旅程
2.1 字符 ≠ 符号:ASCII、Unicode 与 UTF-8 的三层关系
很多前端开发者把“字符”当成视觉符号——看到®就以为它是一个独立存在的图形。这是最大的认知偏差。字符的本质是抽象概念,而我们屏幕上看到的图形,只是这个概念在特定编码规则和字体支持下的一次具象化呈现。要真正掌控特殊字符,必须厘清 ASCII、Unicode、UTF-8 这三者的分工与协作。
ASCII(American Standard Code for Information Interchange):这是所有现代字符编码的起点,诞生于 1963 年。它用 7 位二进制数(0~127)定义了 128 个字符,包括英文大小写字母(A-Z, a-z)、数字(0-9)、基本标点(., !, ?)和控制字符(换行
\n、制表符\t)。它的核心限制在于:只能表示 128 个字符,且完全不支持中文、日文、阿拉伯文等任何非拉丁语系文字。今天你在代码里写的let name = "张三";,里面的张字根本不在 ASCII 表里——这正是问题的起点。Unicode(Universal Character Set):为解决 ASCII 的局限,Unicode 联盟在 1991 年提出统一字符集标准。它的目标很朴素:给世界上每一种人类使用的字符分配一个唯一的数字编号,这个编号叫Code Point(码点)。比如:
A的码点是U+0041€(欧元符号)的码点是U+20AC©(版权符号)的码点是U+00A9𠮷(日本汉字,Unicode 3.1 新增)的码点是U+20BB7
注意
U+开头的表示法——U代表 Unicode,+后面是十六进制码点。Unicode 本身不规定这些码点如何存储为字节,它只负责“命名”和“编号”。截至 Unicode 15.1(2023 年发布),已定义超过 14 万个字符,覆盖 168 种书写系统。你可以把它想象成一本全球字符的“身份证登记簿”,每个字符都有唯一 ID,但 ID 本身不是身份证照片。UTF-8(Unicode Transformation Format - 8-bit):这才是真正落地的“存储方案”。UTF-8 是 Unicode 的一种编码实现方式,它定义了如何把 Unicode 码点转换成实际的字节序列(即 0 和 1 的组合),以便计算机存储和传输。它的设计哲学是向后兼容 ASCII:所有 ASCII 字符(码点
U+0000到U+007F)在 UTF-8 中占用 1 个字节,且字节值与 ASCII 完全一致。这意味着所有纯英文文本,无论用 ASCII 还是 UTF-8 打开,结果都一样。而对于超出 ASCII 范围的字符,UTF-8 采用可变长度编码:- 码点
U+0080~U+07FF(如é,ñ,α)→ 占用 2 个字节 - 码点
U+0800~U+FFFF(如©,€, 中文常用字)→ 占用 3 个字节 - 码点
U+10000~U+10FFFF(如𠮷, 表情符号😀)→ 占用 4 个字节
举个具体例子:
©的 Unicode 码点是U+00A9(十进制 169)。按 UTF-8 规则,它落在 2 字节区间,计算过程如下:- 将 169 转为二进制:
10101001 - UTF-8 2 字节模板:
110xxxxx 10xxxxxx - 把
10101001的 8 位填入模板的x位(高位补 0 对齐):00000000 10101001→ 实际只需后 11 位00000010101001 - 拆成两组:前 5 位
00000+ 后 6 位0101001 - 填入模板:
1100000010101001→ 十六进制为C2 A9
所以,当你在 HTML 文件里直接输入
©,保存为 UTF-8 编码时,文件里实际存储的是两个字节0xC2 0xA9。如果这个文件被错误地当作 ISO-8859-1(一种单字节编码,0xC2对应字符Â,0xA9对应©)来解析,就会显示成©——这就是你看到的“乱码”真相:不是字符错了,是解码规则用错了。- 码点
提示:在 VS Code 中,右下角显示的“UTF-8”只是编辑器的声明,不代表文件真实编码。你可以用命令行验证:
file -i your-file.html(Linux/macOS)或certutil -hashfile your-file.html MD5(Windows)配合在线工具分析字节。我曾遇到一个项目,所有.js文件在编辑器里都显示 UTF-8,但file -i结果却是iso-8859-1,根源是团队共用的脚手架模板里iconv转换脚本写错了参数。
2.2 前端字符处理的四大关键节点
一个字符从开发者敲下键盘,到最终呈现在用户屏幕上,要经过至少四个关键节点,每个节点都可能成为“乱码”的温床。忽略任何一个,都可能导致前功尽弃。
源文件编码(Source File Encoding)
这是旅程的起点。你写的.html、.js、.css文件本身,是以什么字节序列保存在磁盘上的?如果编辑器声明 UTF-8,但实际保存为 GBK(常见于 Windows 记事本),那么即使后续所有环节都正确,初始字节就已错。验证方法:用十六进制编辑器(如 HxD)打开文件,查看开头几个字节。UTF-8 文件通常没有 BOM(Byte Order Mark),但若存在,应为EF BB BF;GBK 文件则无此特征,中文字符多以0xB0~0xF7开头。HTTP 响应头声明(HTTP Response Header)
当浏览器请求一个 HTML 文件时,服务器通过 HTTP 响应头告诉浏览器:“这个文件是用什么编码写的”。关键头是Content-Type: text/html; charset=utf-8。如果服务器没发这个头,或发的是charset=gbk,浏览器就会按错误编码解析整个文档。注意:<meta charset="utf-8">是 HTML 文档内的后备声明,优先级低于 HTTP 头。实测数据:在 Chrome 中,若 HTTP 头缺失charset,浏览器会先尝试根据<meta>标签,再根据 HTML 内容启发式猜测(如检测<!DOCTYPE html>后是否有charset),但这种猜测在复杂页面中极不可靠。HTML 解析与渲染(HTML Parsing & Rendering)
浏览器拿到字节流后,按声明的编码规则将其解码为 Unicode 码点,再构建 DOM 树。此时<span>©</span>中的©已是 Unicode 码点U+00A9。但渲染还依赖下一步——字体支持。字体回退机制(Font Fallback)
即使码点正确,如果当前 CSS 指定的字体(如font-family: "Helvetica Neue")不包含U+00A9这个字形(glyph),浏览器会启动“字体回退”:依次尝试列表中的下一个字体,直到找到能绘制该字符的字体。如果所有指定字体都不支持,最终可能显示为方块□或系统默认的“缺失字符”符号。这也是为什么同一段€在 Windows 和 macOS 上显示效果不同——系统预装字体库差异巨大。例如,Segoe UI(Win)和San Francisco(macOS)对 Unicode 14.0 新增字符的支持度就不同。
这四个节点环环相扣。我曾调试一个跨国电商项目,问题现象是:德语区用户看到的价格符号€正常,但阿拉伯语区用户看到的是方块。排查发现,HTTP 响应头正确,HTML<meta>正确,但 CSS 中font-family只写了Arial, sans-serif,而Arial在部分 Android 设备上对U+20AC(€)的支持不完整。解决方案不是改编码,而是扩充字体栈:font-family: "Segoe UI", "Helvetica Neue", "Noto Sans", sans-serif,其中Noto Sans是 Google 开发的开源字体,目标是“涵盖所有 Unicode 字符”。
2.3 前端常用特殊字符的分类与选型逻辑
前端日常接触的“特殊字符”,按其来源和使用方式可分为四类,每类有其不可替代的适用场景和潜在陷阱:
| 类别 | 典型字符 | 使用方式 | 核心优势 | 主要风险 | 适用场景 |
|---|---|---|---|---|---|
| HTML 实体字符(Named Entities) | ©,®,™, | 在 HTML 标签内直接书写 | 语义清晰、无需担心编码、老浏览器兼容性好 | 字符集有限(仅约 250 个),无法覆盖 emoji 或生僻字 | 版权声明、商标标注、排版空格控制 |
| HTML 数字字符引用(Decimal/Hex Entities) | ©,©,€ | &#+ 十进制/十六进制码点 +; | 覆盖所有 Unicode 字符,精确控制 | 书写冗长,可读性差,易输错 | 动态插入 Unicode 字符、规避 XSS(需谨慎) |
| CSS Unicode 转义(CSS Escape) | \00a9,\20AC | 在 CSS 属性值中使用反斜杠加 Unicode 码点 | 可用于content伪元素、字体图标、动态样式 | 仅限 CSS 上下文,JS 中无效,需注意转义规则 | 伪元素生成符号、自定义字体图标、避免 HTML 注入 |
| JavaScript 字符串字面量 | "©","€",String.fromCodePoint(0x20AC) | 直接在 JS 字符串中书写或 API 生成 | 最灵活,可编程操作,支持所有 Unicode | 文件编码错误时直接崩溃(SyntaxError) | 动态文本拼接、国际化 i18n、API 请求参数构造 |
选型不是凭感觉,而是基于上下文约束。例如,你想在按钮上显示“下载 ⬇️”,如果用 HTML 实体↓(↓),它和⬇️(U+2B07 + U+FE0F)是不同的字符——前者是纯符号,后者是带变体选择符的表情。在移动端,↓渲染更稳定;但在需要情感表达的社交场景,就必须用⬇️。再比如, (不间断空格)和 (普通空格)在 HTML 中表现一致,但 在 CSSwhite-space: nowrap下仍保持不换行特性,而 会被当作普通空格处理。这些细节,决定了你是写出“能用”的代码,还是“稳健”的代码。
3. 实操详解:从编码验证到字符注入的全流程
3.1 验证与修复:三步定位字符编码问题
当用户报告“页面出现乱码”,不要急于改代码。先做三步诊断,90% 的问题能在 5 分钟内定位。
第一步:确认浏览器实际使用的编码
在 Chrome 中,右键页面 → “查看页面源代码” → 查看右下角状态栏(或按Ctrl+Shift+I打开 DevTools →Settings→Preferences→Appearance→ 勾选Show encoding in title bar)。这里显示的是浏览器当前解析该页面所用的编码,它可能与<meta>声明或 HTTP 头不一致。如果显示ISO-8859-1或GBK,而你期望是UTF-8,问题就出在 HTTP 头或<meta>标签。
第二步:检查 HTTP 响应头
在 DevTools 的Network标签页,刷新页面,点击主 HTML 请求 →Headers→Response Headers。查找Content-Type字段。理想值是text/html; charset=utf-8。如果缺失charset,或值为gbk、iso-8859-1,这就是根源。修复方式取决于你的服务端:
- Node.js (Express):
app.use((req, res, next) => { res.set('Content-Type', 'text/html; charset=utf-8'); next(); }); - Nginx:在
server块中添加charset utf-8; - Apache:在
.htaccess中添加AddDefaultCharset UTF-8
第三步:验证源文件真实编码
如果前两步都正确,问题可能出在文件本身。用命令行验证:
# Linux/macOS file -i index.html # 输出示例:index.html: text/html; charset=utf-8 # 如果显示 charset=iso-8859-1,用 iconv 转换 iconv -f iso-8859-1 -t utf-8 index.html > index_fixed.html # Windows (PowerShell) Get-Content .\index.html -Encoding OEM | Set-Content .\index_fixed.html -Encoding UTF8注意:
iconv的-f参数指定源编码,-t指定目标编码。如果不确定源编码,可用enca工具探测:enca -L zh index.html(探测中文编码)。
我曾遇到一个棘手案例:一个 Vue CLI 项目,开发环境一切正常,部署到 Nginx 后部分中文变乱码。排查发现,Vue CLI 的vue-cli-service build生成的index.html是 UTF-8,但 Nginx 配置中charset指令被注释掉了,导致 Nginx 默认用ISO-8859-1发送响应。解决方案不是改前端,而是在 Nginx 配置中取消注释并明确设置charset utf-8;。
3.2 HTML 层:实体字符的正确用法与避坑指南
HTML 实体字符是前端最安全的特殊字符表达方式,但用法有严格规范。
基础语法与常见误区
- 正确写法:
©、®、™、 、“(左双引号)、”(右双引号) - 错误写法:
©(缺少分号)、©(分号后多空格)、©(大小写敏感,必须小写) 的特殊性:它是“不间断空格”,在 HTML 中不会被折叠,也不会在行尾断行。常用于防止“单价:¥”被拆成两行。但它不是用来替代 CSSmargin或padding的布局工具——滥用会导致语义混乱和 SEO 不友好。
动态插入实体的安全实践
直接拼接字符串插入实体是危险的:
// ❌ 危险!可能引发 XSS const userText = "<script>alert('xss')</script>"; document.getElementById('content').innerHTML = `© ${userText}`; // © 被当作纯文本,但 userText 中的 script 会执行 // ✅ 安全!先转义再插入 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } document.getElementById('content').innerHTML = `© ${escapeHtml(userText)}`;不可见字符的隐形杀手
除了可见符号,HTML 中还有大量不可见字符,它们对布局和逻辑有微妙影响:
­(软连字符):在单词内允许断行的位置插入,仅在必要时显示连字符。<p>Re­spoon­si­bil­i­ty</p>在窄屏下可能断行为Re- spoon- si- bil- i- ty。‍(零宽连接符):强制将相邻字符连接为一个字形,常用于 emoji 组合,如👨💻(U+1F468 U+200D U+1F4BB)。‌(零宽非连接符):阻止连接,如لا(阿拉伯语)中防止字母连写。
这些字符在编辑器中不可见,但会影响 DOM 结构。调试时,可在 DevTools 的 Elements 面板中右键 → “Edit as HTML”,查看原始 HTML 字符。
3.3 CSS 层:Unicode 转义与伪元素实战
CSS 中的 Unicode 转义主要用于content属性,这是生成装饰性符号最优雅的方式。
基础语法与转义规则
- 格式:
\+ 1~6 位十六进制码点,后面必须跟一个非十六进制字符(如空格、分号、括号)作为终止符。 - 正确:
content: "\00a9";、content: "\20AC ";(注意\20AC后的空格) - 错误:
content: "\00a9";(缺少终止符,会被后续字符吞并)、content: "\u00a9";(\u是 JS 语法,CSS 不识别)
实战:用伪元素构建响应式图标系统
不用引入 iconfont,纯 CSS 就能实现轻量图标:
/* 版权符号 */ .copyright::before { content: "\00a9"; /* © */ font-size: 0.8em; margin-right: 0.2em; } /* 欧元符号 */ .price::before { content: "\20AC"; /* € */ font-size: 0.9em; } /* 自定义箭头(避免使用 img 标签) */ .arrow-right::after { content: "\2794"; /* ➔ */ display: inline-block; transform: rotate(0deg); transition: transform 0.2s; } .arrow-right:hover::after { transform: rotate(90deg); }字体图标 fallback 方案
当 Unicode 字符在某些设备上缺失时,提供降级:
.icon-download::before { content: "\2193"; /* ↓ 纯符号 fallback */ font-family: "CustomIconFont", "Segoe UI", "Helvetica Neue", sans-serif; } /* 如果 CustomIconFont 加载失败,浏览器会自动回退到 Segoe UI */实操心得:在 CSS 中使用 Unicode 转义时,务必在码点后加空格或分号。我曾因
\20AC后没空格,导致后续的;被吞并,整个 CSS 规则失效,花了 2 小时才定位到这个空格问题。
3.4 JavaScript 层:字符串编码与 URL 安全处理
JS 中的字符处理是动态场景的核心,错误极易引发数据丢失或安全漏洞。
encodeURIvsencodeURIComponent:何时用哪个?
两者都对字符串进行 URI 编码(将非 ASCII 字符转为%XX格式),但范围不同:
encodeURI():编码除/,?,#,&,=等 URI 保留字符外的所有字符。适用于整个 URI 字符串。encodeURI("https://example.com/search?q=©&page=1"); // 输出: "https://example.com/search?q=%C2%A9&page=1" —— / ? & = 未被编码encodeURIComponent():编码所有非字母数字字符,包括 URI 保留字符。适用于URI 的单个组件(如查询参数值)。encodeURIComponent("© price: €99"); // 输出: "%C2%A9%20price%3A%20%E2%82%AC99" —— 空格、冒号、€ 全被编码
错误用法示例:
// ❌ 错误:用 encodeURI 编码单个参数,导致 & 被编码,破坏 query string 结构 const url = `https://api.com/data?q=${encodeURI("foo&bar")}`; // 结果: https://api.com/data?q=foo%26bar → 后端解析为 q=foo&bar(bar 被当新参数) // ✅ 正确:用 encodeURIComponent 编码参数值 const url = `https://api.com/data?q=${encodeURIComponent("foo&bar")}`; // 结果: https://api.com/data?q=foo%26bar → 后端正确解析 q="foo&bar"处理来自表单或 API 的“乱码”字符串
有时后端返回的 JSON 中,中文是乱码(如"name":"å¼ ä¸"),这通常是后端未正确设置响应头Content-Type: application/json; charset=utf-8。前端可临时修复:
// 将乱码字符串按错误编码(如 latin1)重新编码为字节,再按 UTF-8 解码 function fixMojibake(str) { if (!str) return str; // 先将字符串转为 latin1 字节(每个字符占 1 字节) const bytes = new Uint8Array(str.length); for (let i = 0; i < str.length; i++) { bytes[i] = str.charCodeAt(i) & 0xFF; } // 再将字节流按 UTF-8 解码 return new TextDecoder('utf-8').decode(bytes); } // 使用 const broken = "å¼ ä¸"; const fixed = fixMojibake(broken); // "张三"Emoji 处理的特殊挑战
Emoji 如👍(U+1F44D)在 JS 中占 2 个码元(surrogate pair),length属性返回 2,但实际是 1 个字符。遍历或截取时需用Array.from()或for...of:
const emoji = "👍"; console.log(emoji.length); // 2 console.log(Array.from(emoji).length); // 1 console.log([...emoji].length); // 1 // 安全截取前 10 个字符(含 emoji) function safeSubstring(str, length) { return Array.from(str).slice(0, length).join(''); }4. 常见问题与排查技巧实录
4.1 高频问题速查表
| 问题现象 | 根本原因 | 快速排查步骤 | 解决方案 |
|---|---|---|---|
HTML 中©显示为© | 文件是 UTF-8 编码,但被浏览器当作 ISO-8859-1 解析 | 1. 查Network→Response Headers是否有charset=utf-82. 查 <meta charset>是否存在且位置在<head>前 1KB3. 用 file -i验证文件真实编码 | 1. 服务器配置Content-Type2. 确保 <meta>在<head>开头3. 用 iconv转换文件编码 |
| Edge 打开 PDF 中特殊字符变乱码 | PDF 文件内嵌字体不支持 Unicode,或浏览器 PDF 查看器渲染引擎缺陷 | 1. 用 Adobe Acrobat 打开同一 PDF,确认是否正常 2. 检查 PDF 创建时的字体嵌入设置 | 1. 生成 PDF 时嵌入完整 Unicode 字体(如 Noto Sans) 2. 提供 PDF 下载链接而非内嵌查看 |
Vue/React 组件中€渲染为方块□ | CSSfont-family指定的字体不包含U+20AC字形 | 1. DevTools →Computed→ 查font-family实际生效值2. 在 Elements面板中右键 →Edit as HTML,复制字符到 Unicode Table 查看码点 | 扩充font-family栈,加入Noto Sans,DejaVu Sans等开源字体 |
fetch请求后端返回中文乱码(如å¼ ä¸) | 后端响应头缺失charset=utf-8,或前端未指定responseType | 1.Network→ 点击请求 →Preview标签页看原始响应2. 查 Response Headers中Content-Type | 1. 后端设置Content-Type: application/json; charset=utf-82. 前端 fetch(url).then(r => r.json())(json()方法自动按 UTF-8 解码) |
<textarea>中粘贴的“”变成Ҡ| 粘贴源(如 Word)使用了 Windows-1252 编码的弯引号,而页面是 UTF-8 | 1. 在textarea的input事件中console.log(e.target.value.charCodeAt(0).toString(16))2. 对比 U+201C(“)和0x93(Windows-1252 弯引号) | 在input事件中监听并替换:value = value.replace(/[\u201C\u201D]/g, '"').replace(/[\u2018\u2019]/g, "'"); |
4.2 独家避坑技巧:那些文档里不会写的细节
技巧 1:VS Code 的“编码陷阱”
VS Code 默认在保存文件时不添加 BOM(Byte Order Mark),这对 UTF-8 是最佳实践。但某些老旧系统(如 IE8)或特定后端框架(如旧版 ASP.NET)可能要求 BOM 来识别 UTF-8。如果你必须添加 BOM,不要用 VS Code 的“Save with Encoding” → “UTF-8 with BOM”,因为这会污染 Git 历史(BOM 是不可见字节)。正确做法:用命令行一次性添加:
# Linux/macOS:为单个文件添加 UTF-8 BOM sed -i '1s/^/\xEF\xBB\xBF/' your-file.js # Windows PowerShell:为所有 .js 文件添加 Get-ChildItem *.js | ForEach-Object { $content = Get-Content $_.FullName -Raw Set-Content $_.FullName ("" + $content) -Encoding UTF8 }技巧 2:<meta charset>的位置玄机
HTML 规范要求<meta charset>必须在<head>中前 1024 字节内,否则浏览器可能忽略它。这意味着:
- ❌ 错误:在
<head>中先引入大型 CSS 文件(<link rel="stylesheet" href="big.css">),再写<meta> - ✅ 正确:
<meta charset="utf-8">是<head>中的第一行(或紧随<!DOCTYPE html>之后)
实测:一个 200KB 的 CSS 文件,如果放在<meta>之前,Chrome 会因超 1024 字节而回退到默认编码(通常是ISO-8859-1),导致整个页面乱码。
技巧 3:Node.jsfs.readFile的编码盲区
在 Node.js 中读取前端文件时,fs.readFile(path, 'utf8')的'utf8'参数仅表示输出字符串的编码,不校验文件真实编码。如果文件是 GBK,fs.readFile会强行按 UTF-8 解码,产生乱码。安全做法是先探测编码:
const fs = require('fs').promises; const detect = require('detect-character-encoding'); async function readFileSafe(path) { const buffer = await fs.readFile(path); const encoding = detect(buffer).encoding; return buffer.toString(encoding); } // 使用 const content = await readFileSafe('./index.html');技巧 4:CSS@font-face的 Unicode 范围精准控制
为减少字体文件体积,只加载需要的字符。unicode-range属性可指定码点范围:
@font-face { font-family: 'MyIconFont'; src: url('icons.woff2') format('woff2'); unicode-range: U+00A9, U+20AC, U+2192-2194, U+2794; /* 只加载 © € → ← ↕ ➔ */ }这样,浏览器只下载包含这些字符的字体子集,而非整个字体文件。
4.3 面试高频考点深度解析
“前端特殊字符”是 2024-2026 年前端面试的隐性热点,尤其在中高级岗位。面试官不考死记硬背,而是考察你对底层原理的理解深度。
考点 1:decodeURIComponent('%C2%A9')为什么返回©?
这不是 JS 的 bug,而是双重解码的结果。%C2%A9是 UTF-8 编码的©(U+00A9)的百分号编码。decodeURIComponent会将其解码为字节0xC2 0xA9,然后按 JS 引擎的默认编码(通常是 UTF-8)解释为 Unicode 字符。但如果 JS 引擎错误地将 `0xC2