明智地使用 lang、dir 和编码声明,并击败 Unicode 规范化与方向性方面的陷阱。
在 Kodokon 中打开本课lang 属性远不止是元数据:它驱动屏幕阅读器的语音、连字符断字(hyphens: auto 没有它就无法工作)、CJK 字形的选择(同一个表意文字在日语和中文中绘制方式不同)、<q> 生成的引号以及 CSS 的 :lang() 选择器。它是可继承的:在 <html> 上用一个 BCP 47 标签声明它(en、en-GB、ja),并在每次语言切换处进行局部覆盖。
<html lang="en">
<p>She said: <q>hello</q>.</p>
<p lang="fr">Elle a dit : <q>bonjour</q>.</p>
</html>dir=rtl 会翻转默认对齐方式,以及方向性中性的字符的视觉顺序。对于无法预测的用户内容,dir=auto 会根据第一个具有强方向性的字符来选择方向。<bdi> 元素会把一个片段从双向算法中隔离出来:没有它,一个后面跟着“:12 分”的阿拉伯语用户名,其标点符号会被重新排序成一堆乱码。
<p dir="auto">مرحبا - inferred direction: RTL</p>
<ul>
<li><bdi>مستخدم</bdi>: 12 points</li>
<li><bdi>Alice</bdi>: 8 points</li>
</ul><meta charset=utf-8> 声明必须出现在文档的前 1024 个字节以内:浏览器会在选定编码之前,在这个窗口范围内运行一次预扫描。如果既没有声明也没有 HTTP 头,默认编码取决于用户的区域设置(在西欧常常是 windows-1252)—— 绝不会是 UTF-8。然而,文件开头的 BOM 会覆盖其他一切,包括 HTTP 头。
const composed = "\u00e9";
const decomposed = "e\u0301";
console.log(composed === decomposed); // false
const nfc = decomposed.normalize("NFC");
console.log(composed === nfc); // truemacOS 以分解形式(NFD)存储某些文件名:因此,来自一次上传的“é”可能与用键盘输入的那个不同。系统性地把所有用户输入在存储或比较之前规范化为 NFC。最后,.length 计的是 UTF-16 码元(code unit),而不是感知到的字符:一个家庭 emoji 组合了七个以上的码元;用 Intl.Segmenter 来计算真正的字素(grapheme)。
<meta charset=utf-8> 声明必须位于何处?<head> 中的任何位置<body> 的前 512 个字节以内dir=auto 如何确定一个元素的方向?<html> 元素上声明的方向=== 可能不相等?