Skip to content

meta 字符编码

<meta charset="UTF-8"> 是 HTML5 中用于声明文档字符编码的简写方式。它确保浏览器正确地解析和显示页面中的文字,是防止乱码问题的第一道防线。

前置知识

阅读本节前,建议先了解:title 标题

基础概念

什么是字符编码

计算机存储和传输的是二进制数据(0 和 1)。字符编码(Character Encoding)就是一套规则,用来将人类可读的字符(如"你"、"A")映射为计算机可处理的数字(二进制序列)。

例如,字符"你"在不同编码下的表示:

编码二进制/十六进制字节数
UTF-8E4 BD A03 字节
GBKC4 E32 字节
GB2312C4 E32 字节
Big5A7 DA2 字节

如果文件实际使用 UTF-8 编码保存,但浏览器用 GBK 去解码,就会出现乱码

为什么需要声明字符编码

浏览器在解析 HTML 文件时,需要知道文件使用的是什么字符编码。如果没有正确告知编码,浏览器只能"猜测",这可能导致:

  • 中文显示为乱码(如 "浣犲ソ" 或 "éÂ")
  • 特殊符号显示异常
  • 表单提交的数据编码错误
  • JavaScript 中的字符串处理出错
html
<!-- 声明字符编码 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>字符编码示例</title>
</head>
<body>
  <p>你好,世界!Hello, World!</p>
  <p>特殊符号: &copy; &reg; &trade; &hearts;</p>
</body>
</html>

语法

HTML5 简写语法

html
<meta charset="UTF-8">

这是 HTML5 引入的简化写法,等价于 HTML4 中的:

html
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

语法要点

  • charset<meta> 标签的一个独立属性
  • 值不区分大小写,但推荐使用标准大写:UTF-8
  • <meta charset> 是一个自闭合标签,不需要结束标签
  • 应该放在 <head> 的最前面
html
<!-- 正确 -->
<meta charset="UTF-8">

<!-- 也可以(不区分大小写),但不推荐 -->
<meta charset="utf-8">
<meta charset="Utf-8">

<!-- 错误!不能使用这种旧写法在 HTML5 中 -->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

详细说明

常见字符编码

编码全称说明中文支持
UTF-8Unicode Transformation Format - 8-bitUnicode 的可变长编码,兼容 ASCII完整支持
GBK国标扩展中国国家标准编码支持简体和繁体
GB2312国标2312GBK 的子集仅支持简体
Big5大五码繁体中文编码仅支持繁体
ISO-8859-1Latin-1西欧语言编码不支持
ASCIIAmerican Standard Code for Information Interchange美国信息交换标准代码不支持

为什么推荐 UTF-8

UTF-8 是当前 Web 开发的标准编码,原因如下:

1. 通用性

UTF-8 可以编码世界上几乎所有的文字系统:

html
<meta charset="UTF-8">
<!-- 以下所有文字都能正确显示 -->
<p>中文:你好世界</p>
<p>日本語:こんにちは</p>
<p>한국어: 안녕하세요</p>
<p>English: Hello World</p>
<p>العربية: مرحبا</p>
<p>Русский: Привет</p>
<p>Emoji: 🎉 🚀 ❤️</p>

2. 兼容 ASCII

UTF-8 对 ASCII 字符(英文字母、数字、基本标点)使用单字节编码,与 ASCII 完全兼容。这意味着纯英文内容的 UTF-8 文件与 ASCII 文件大小相同。

3. 可变长编码

UTF-8 使用 1~4 个字节编码一个字符,在文件大小和多语言支持之间取得了良好的平衡:

字符范围字节数示例字符
U+0000 ~ U+007F1 字节A-Z, a-z, 0-9, 基本标点
U+0080 ~ U+07FF2 字节拉丁扩展、希腊字母、西里尔字母
U+0800 ~ U+FFFF3 字节中日韩文字(CJK)
U+10000 ~ U+10FFFF4 字节Emoji、生僻字、古代文字

4. 行业标准

  • HTML5 规范推荐使用 UTF-8
  • W3C 和 WHATWG 建议所有网页使用 UTF-8
  • 所有现代浏览器默认编码为 UTF-8
  • JSON、XML、大多数 API 都默认使用 UTF-8

编码问题的根源分析

场景一:文件编码与 meta 声明不一致

html
<!-- 文件以 GBK 编码保存,但声明了 UTF-8 -->
<!-- 错误!编码声明与实际编码不匹配 -->
<meta charset="UTF-8">
<!-- 文件中的中文会显示为乱码 -->
<p>你好世界</p>  <!-- 实际保存为 GBK,但浏览器用 UTF-8 解码 -->

解决方法:确保文件的保存编码与 charset 声明一致。

场景二:没有声明编码

html
<!-- 没有声明字符编码 -->
<head>
  <title>测试</title>
</head>
<body>
  <p>中文内容可能显示异常</p>
</body>

当浏览器没有找到编码声明时,它会尝试以下方式检测编码:

  1. BOM(字节顺序标记):如果文件以 UTF-8 BOM 开头
  2. Content-Type 响应头:服务器的 HTTP 响应头
  3. 浏览器默认编码:通常为 UTF-8,但不保证

场景三:HTTP 响应头与 meta 声明冲突

如果服务器在 HTTP 响应头中指定了编码,它通常会覆盖 <meta charset> 的声明:

Content-Type: text/html; charset=GBK
html
<!-- 即使这里声明了 UTF-8,如果 HTTP 响应头指定了 GBK -->
<!-- 浏览器可能会使用 GBK 解码 -->
<meta charset="UTF-8">

优先级

编码检测优先级:HTTP 响应头 > BOM > <meta charset> > 浏览器猜测。确保 HTTP 响应头与 meta 声明一致。

场景四:表单提交的编码问题

如果页面的编码不正确,用户通过表单提交的数据也可能出现乱码:

html
<!-- 表单的编码应该与页面编码一致 -->
<form action="/submit" method="post" accept-charset="UTF-8">
  <input type="text" name="username" placeholder="请输入用户名">
  <button type="submit">提交</button>
</form>

charset 的位置要求

<meta charset> 应该放在 <head>尽可能靠前的位置。规范建议在文件的前 1024 字节内出现:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <!-- charset 必须在最前面 -->
  <meta charset="UTF-8">

  <!-- 其他 meta 标签 -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="页面描述">
  <title>页面标题</title>
</head>

严重后果

如果 <meta charset> 出现得太晚(超过前 1024 字节),浏览器可能已经开始用错误的编码解析了页面中的非 ASCII 字符,导致这些字符无法正确恢复。

其他编码声明的写法(历史对比)

html
<!-- HTML5 推荐写法 -->
<meta charset="UTF-8">

<!-- HTML4 写法(仍然有效但不推荐) -->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<!-- XHTML 写法 -->
<?xml version="1.0" encoding="UTF-8"?>
方式代码长度HTML5 兼容推荐程度
<meta charset="UTF-8">完全兼容强烈推荐
<meta http-equiv="Content-Type">兼容不推荐
XML 声明不适用不适用于 HTML

实战示例

编码检测工具

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>编码检测工具</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      max-width: 700px;
      margin: 40px auto;
      padding: 0 20px;
      line-height: 1.6;
    }
    .info-card {
      background: #f8f9fa;
      border: 1px solid #dee2e6;
      border-radius: 8px;
      padding: 20px;
      margin: 15px 0;
    }
    .info-card dt {
      font-weight: bold;
      color: #0066cc;
    }
    .info-card dd {
      margin: 5px 0 15px;
      padding-left: 20px;
    }
    .char-table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 15px;
    }
    .char-table th, .char-table td {
      border: 1px solid #dee2e6;
      padding: 8px 12px;
      text-align: left;
    }
    .char-table th {
      background: #f0f0f0;
    }
  </style>
</head>
<body>
  <h1>字符编码检测</h1>
  <p>当前页面的编码信息:</p>

  <dl class="info-card">
    <dt>document.charset</dt>
    <dd id="doc-charset">-</dd>

    <dt>document.characterSet</dt>
    <dd id="doc-charset2">-</dd>

    <dt>document.inputEncoding</dt>
    <dd id="doc-input-encoding">-</dd>
  </dl>

  <h2>UTF-8 字符编码示例</h2>
  <table class="char-table">
    <thead>
      <tr>
        <th>字符</th>
        <th>Unicode 码点</th>
        <th>UTF-8 编码</th>
        <th>字节数</th>
      </tr>
    </thead>
    <tbody>
      <tr><td>A</td><td>U+0041</td><td>41</td><td>1</td></tr>
      <tr><td>你</td><td>U+4F60</td><td>E4 BD A0</td><td>3</td></tr>
      <tr><td></td><td>U+2764</td><td>E2 9D A4</td><td>3</td></tr>
      <tr><td>🎉</td><td>U+1F389</td><td>F0 9F 8E 89</td><td>4</td></tr>
      <tr><td>🚀</td><td>U+1F680</td><td>F0 9F 9A 80</td><td>4</td></tr>
    </tbody>
  </table>

  <script>
    // 显示文档编码信息
    document.getElementById('doc-charset').textContent = document.charset;
    document.getElementById('doc-charset2').textContent = document.characterSet;
    document.getElementById('doc-input-encoding').textContent = document.inputEncoding;

    // 生成字符编码表
    var tbody = document.querySelector('.char-table tbody');
    var testChars = [
      { char: 'A', desc: 'ASCII 字符' },
      { char: '\u00E9', desc: 'Latin 扩展' },
      { char: '\u4F60', desc: 'CJK 字符' },
      { char: '\uD83D\uDE00', desc: 'Emoji' }
    ];

    testChars.forEach(function(item) {
      var tr = document.createElement('tr');
      var codePoint = item.char.codePointAt(0);
      var bytes = new TextEncoder().encode(item.char);
      tr.innerHTML =
        '<td>' + item.char + ' (' + item.desc + ')</td>' +
        '<td>U+' + codePoint.toString(16).toUpperCase().padStart(4, '0') + '</td>' +
        '<td>' + Array.from(bytes).map(function(b) {
          return b.toString(16).toUpperCase().padStart(2, '0');
        }).join(' ') + '</td>' +
        '<td>' + bytes.length + '</td>';
      tbody.appendChild(tr);
    });
  </script>
</body>
</html>

各种编码的对比效果

html
<!-- 以 GBK 编码保存此文件(演示用,实际不推荐) -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <!-- 必须与文件实际编码一致 -->
  <meta charset="GBK">
  <title>GBK 编码示例</title>
</head>
<body>
  <p>如果文件以 GBK 保存且声明 GBK,中文可以正常显示。</p>
  <p>但如果声明 UTF-8,就会出现乱码。</p>
</body>
</html>

注意事项

1. 确保文件保存编码与声明一致

这是最常见的编码问题。检查编辑器的编码设置:

  • VS Code:右下角状态栏显示编码,点击可切换
  • Sublime Text:File > Save with Encoding
  • 记事本:另存为时选择编码

2. 不要使用 UTF-8 BOM

UTF-8 BOM(字节顺序标记)是文件开头的三个隐藏字节 \xEF\xBB\xBF。虽然不会影响 UTF-8 解码,但可能导致:

  • DOCTYPE 前面出现不可见字符
  • CSS 文件中的 @charset 失效
  • 某些旧版浏览器的兼容问题

建议

保存文件时使用 UTF-8 without BOM(无 BOM 的 UTF-8)。

3. 旧编码的迁移

如果维护使用 GBK/GB2312 编码的旧网站,迁移到 UTF-8 需要注意:

html
<!-- 旧页面 -->
<meta http-equiv="Content-Type" content="text/html; charset=GBK">

<!-- 迁移后 -->
<meta charset="UTF-8">

迁移步骤:

  1. 将文件编码转换为 UTF-8(使用编辑器或命令行工具)
  2. 修改 <meta charset> 声明
  3. 检查数据库连接编码
  4. 检查服务器 HTTP 响应头
  5. 全面测试

4. 数据库编码也要匹配

sql
-- MySQL 数据库和表也建议使用 UTF-8
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

最佳实践

1. 始终使用 UTF-8

html
<meta charset="UTF-8">

2. 放在 head 的最前面

html
<head>
  <meta charset="UTF-8">
  <!-- 其他内容... -->
</head>

3. 统一全栈编码

确保以下环节全部使用 UTF-8:

  • HTML 文件的 <meta charset>
  • HTTP 响应头 Content-Type
  • 数据库编码
  • 后端代码的默认编码
  • API 接口的编码

4. 使用 utf8mb4 而非 utf8

在 MySQL 中,utf8 只支持最多 3 字节的 UTF-8 字符,不支持 4 字节的 Emoji。应使用 utf8mb4

sql
-- 推荐
CHARACTER SET utf8mb4

-- 不推荐(不支持 Emoji)
CHARACTER SET utf8

5. 使用 TextEncoder/TextDecoder 处理编码

javascript
// JavaScript 中正确处理编码
var encoder = new TextEncoder();  // 默认 UTF-8
var bytes = encoder.encode('你好');
// Uint8Array(6) [228, 189, 160, 229, 165, 189]

var decoder = new TextDecoder('utf-8');
var text = decoder.decode(bytes);
// "你好"

下一节

继续学习:meta 视口设置

参考链接