template.content.clonenode(true)是唯一安全的克隆方式,因template内容以只读documentfragment形式存在content属性中,直接读取innerhtml/innertext为空,字符串替换易引发xss且破坏结构。

template.content.cloneNode(true) 是唯一安全的克隆方式
直接读取 template.innerHTML 或 template.innerText 会返回空字符串,因为浏览器把内容存为只读的 DocumentFragment,藏在 content 属性里。用字符串替换(比如 innerHTML.replace('{{name}}', 'Alice'))不仅漏掉嵌套标签、自闭合元素(如 <input>),还会绕过 XSS 防护机制。
正确做法只有一步:template.content.cloneNode(true)。这个克隆结果是干净、独立、可操作的 DOM 实例——样式未生效、脚本未执行、图片未加载,但节点关系完整,事件绑定和表单状态都可后续补全。
- 不 clone 直接
appendChild(template.content)会导致模板内容被“搬走”,第二次调用时content为空 - 克隆后若含
id属性,必须重设(如el.id = 'card-' + Date.now()),否则多个实例间 ID 冲突,document.getElementById()行为不可预测 - 克隆节点默认不带事件监听器,需在插入后手动绑定,或统一在工厂函数里封装
填充数据必须用语义化 API,不是 innerHTML 拼接
模板里放的是结构,不是字符串容器。用户输入、API 返回的文本内容,一律走 textContent;属性变更(如 src、href、data-*)用 setAttribute();表单控件的初始值不能靠写死 value="xxx",得显式赋值 el.value = userData.phone——否则克隆后实际值仍是空。
-
textContent自动转义 HTML 字符,防注入;innerHTML只在你明确需要渲染 HTML 片段时才用(且必须确保来源可信) - 含
<label for="email"></label>的表单,克隆后要同步更新for和对应input#email的id,否则点击 label 不聚焦 - 使用
dataset存上下文标识(如card.dataset.userId = '123'),比塞隐藏字段更轻量、更语义化
template 标签位置不能嵌套,必须在 document.body 外层
template 必须作为顶层元素存在,不能包在 div、section 或其他组件内部。一旦被包裹,部分浏览器(尤其是 Safari 15.4 之前)会丢弃其 content,导致 template.content 为空 DocumentFragment,克隆后什么都没有。
- 典型错误写法:
<div class="page"><template id="item">...</template></div> - 正确写法:把
<template></template>放在下同级,或末尾(但不要进任何其他元素) - 如果用构建工具预处理 HTML,注意它是否会在输出时意外包裹
template
如何与 Custom Elements 配合避免语义退化
很多人在 Shadow DOM 里塞一堆 div,再靠 aria-label 补救,这等于放弃语义,又想拿无障碍成果。真正可复用的组件,结构契约不能丢。
- 如果组件本质是卡片,注册为
<product-card></product-card>,内部模板优先用<article></article>包裹,而不是<div role="article"> <li>对外暴露 <code><slot></slot>时,明确命名语义用途,如<slot name="title"></slot>对应h3级别标题,而非泛用default - 避免在自定义元素内部硬写
<div class="header">,改用 <code><header></header>;图文组合优先用<figure><figcaption></figcaption></figure>而非div套img+p最常被忽略的一点:语义结构一旦进了 Shadow DOM,外部 CSS 选不到,但 screen reader 仍能按 DOM 树读取——所以语义错位不会立刻报错,却会让组件在真实辅助场景中失效。











