学习前提
理解HTML标签、实体和字符编码声明;动态展示实验还需会调用textContent。
本课完成后你能做什么
分清文件编码、HTML解析和页面显示,修复乱码并安全展示代码。
先理解机制
浏览器先将响应字节按编码解码为字符,再按HTML规则构建DOM。保存文件时选择UTF-8,与文档中的meta charset保持一致;仅修改meta不能修复已经错误保存的字节。出现乱码时先检查编辑器保存编码和Network响应头,再检查HTML声明,不要反复复制乱码来修补。
小于号可能开始标签,&可能开始字符引用。要让读者看到<p>而不是生成段落,需要在HTML源码中写<p>。pre保留排版空白,code说明文本是代码,两者不会自动转义内容。把真实script直接塞进pre仍可能执行,预格式化不是安全边界。
动态文本使用textContent,静态HTML源文件使用实体编码。这是两条不同的写入路径:textContent接收原字符串,浏览器负责作为文本节点展示,不应预先把&改成&,否则用户会看到多余实体。URL编码则用于URL组件,不能替代HTML转义。
普通空格在HTML排版中会折叠。用CSS的间距和布局表达视觉关系,不要用几十个 对齐栏目。不可断空格仅适合确实不希望分开的文本片段;大量使用会破坏窄屏换行。
可运行示例
运行方式:将HTML示例保存为独立的.html文件并在浏览器打开;含脚本的示例使用现代浏览器。资源文件与多文件模块另按正文要求准备。
<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>文本与代码</title>
<h1>字符与代码</h1>
<p>价格:¥12;关系:3 < 5;符号:A & B</p>
<pre><code><p>Hello</p></code></pre>
<pre><code id="example"></code></pre>
<script>
document.querySelector('#example').textContent = '<img src="missing" alt="example">';
</script></html>
推演与验证
第一步查看页面,应看到中文、¥和小于号。第二步检查DOM:两个code中只有文字,不应出现p或img子元素。第三步把textContent换成innerHTML,观察img节点及失败请求的出现,再恢复原版。这展示的是写入方式差异,不要在真实用户输入上试验。
常见错误与边界
浏览器展示正常不代表源文件组织正确,检查器可能修复无效嵌套。不要手写只替换小于号的通用过滤器;不同输出上下文有不同要求,文本节点优先使用原生文本API。
动手练习
制作一段教学文字,同时展示标签、&符号和两行带缩进代码。验收:复制代码后保留原始字符,DOM中没有意外图片或脚本节点。
参考解法与理由
把源码直接赋给code.textContent,并用pre提供空白保留。用textContent回读后与原字符串严格比较,可以发现二次转义;不要以页面截图看起来相同作为唯一标准。
深入实验:转义应该发生在哪一层
把文本当HTML解析,和把它作为文本节点写入,是两个不同操作。对textContent预先做实体编码会重复转义。下面仅在脱离页面的div中比较,不触发外部资源请求,也不使用攻击脚本。
在现代浏览器Console中独立运行这个实验;它不替换上方主示例。
const raw = '<strong>A & B</strong>';
const text = document.createElement('div');
text.textContent = raw;
const markup = new DOMParser().parseFromString(raw, 'text/html').body;
const doubled = document.createElement('div');
doubled.textContent = '<strong>A & B</strong>';
console.log(text.children.length, markup.children.length);
console.log(text.textContent, markup.textContent);
console.log(doubled.textContent);
console.assert(text.textContent === raw);
console.assert(markup.firstElementChild.tagName === 'STRONG');预期观察
子元素数量为0和1。text保留完整标签文字,markup只读回A & B,doubled则把<等实体字符原样显示。
原因、取舍与适用边界
浏览器在把textContent序列化为innerHTML时会自动显示必要的实体,不代表原始文本被业务代码改变了。比较文本应使用textContent,比较结构应检查元素节点。HTML实体编码不能用于SQL参数化或URL组件编码,这些是不同解释器的边界。若确实需要允许有限富文本,使用明确的元素/属性策略和经过维护的净化器,而不是把replace链当作完整安全机制。
进阶练习
给代码预览加一个复制按钮,应复制innerHTML还是textContent?如何检测二次转义?
解法与推理
读取code.textContent,因为它保存展示给读者的原始字符。把复制前的字符串与最初源码严格比较;包含<、>、&、引号、中文和换行的样例能同时覆盖多种容易混淆的字符。