本身不保存状态,必须用document.importnode(tmpl.content, true)克隆才能保留表单控件的checked、value等运行时状态;直接innerhtml赋值或clonenode会重置状态、破坏可访问性。

<template></template> 本身不渲染、不执行脚本、不加载资源,直接插入或 innerHTML 赋值会丢表单状态、破坏可访问性——它只是容器,不是组件。
为什么不能直接用 innerHTML = template.innerHTML
浏览器解析 <template></template> 时完全跳过其内容:里面的 <script></script> 不执行、<img src> 不发起请求、<input checked> 的选中态不保留、data- 属性不被识别。更关键的是,用 cloneNode(true) 复制后插入 DOM,<select></select> 当前选项、<textarea></textarea> 光标位置、<input type="file"> 已选文件列表全都会丢失。
常见错误现象:
- 页面空白,或只显示文本不触发交互
- 表单控件“重置”为初始值,用户输入瞬间消失
- 屏幕阅读器无法定位
<main></main>,报错ARIA: main role must not be a descendant of another landmark
必须用 document.importNode() 克隆片段
这是唯一能正确保留所有运行时状态的克隆方式。流程固定为四步:取模板 → 克隆片段 → 查找绑定点 → 填充并挂载。
实操要点:
- 获取模板:
const tmpl = document.getElementById('card-template') - 深克隆内容:
const fragment = document.importNode(tmpl.content, true)(第二个参数true不可省) - 填充文本时禁用
innerHTML写用户输入,改用textContent防 XSS - 绑定节点建议用
data-bind属性,如<h2 data-bind="title"></h2>,避免侵入式 class 名
<main></main> 和 <nav></nav> 必须语义准确且不可嵌套
<main></main> 是文档级唯一核心区域,每个 HTML 文件中只能出现一次,且不能嵌套在 <article></article>、<section></section> 或其他 landmark 中。否则 NVDA、VoiceOver 的“跳转到主要内容”快捷键(如 Ctrl+Alt+O)失效,搜索引擎也可能降权。
<nav></nav> 则必须带 aria-label,尤其当页面含多个导航区时:
- 主导航:
<nav aria-label="主导航"></nav> - 页脚链接:
<nav aria-label="友情链接"></nav> - 面包屑:
<nav aria-label="当前位置"></nav>
漏掉 aria-label 会导致屏幕阅读器反复读出 “navigation”,毫无区分度。
模板复用必须避开服务端 include 和字符串拼接
纯静态环境(如 GitHub Pages、Netlify)不支持 PHP include 或 Node 模板引擎。常见错误是把 nav.html 当作可直接嵌入的片段,却忘了浏览器根本不解析 @@include 或 require()。
安全做法只有两种:
- 用
<template id="nav"></template>定义结构,JS 中通过document.importNode()动态挂载 - 服务端由构建脚本(如 esbuild 插件)预编译合并,输出单一 HTML 文件
注意:Safari 旧版本对 template.content 支持不稳定,需兜底:if (!tmpl.content) tmpl.content = document.createDocumentFragment()。
最易被忽略的一点:所有绑定逻辑必须在克隆后、挂载前完成;一旦插入 DOM,再修改 textContent 或事件监听器,就可能触发重复渲染或焦点丢失。状态同步和 DOM 生命周期要严格分离。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











