必须用document.importNode(tmpl.content, true)克隆模板,才能保留表单状态、脚本上下文及Shadow DOM兼容性;id需动态生成防冲突;样式JS须剥离重执行并注入shadowRoot。

template.content克隆必须用document.importNode()
直接innerHTML = tmpl.innerHTML会丢掉表单状态和脚本上下文,cloneNode(true)也不能保留、select选中项等原生行为。只有document.importNode(tmpl.content, true)能安全复用模板内容,且兼容后续挂入Shadow DOM的场景。
- 错误写法:
container.innerHTML = tmpl.innerHTML→textarea值丢失、input[type="checkbox"]checked态清空 - 正确路径:从
tmpl.content克隆,不是tmpl本身;tmpl只是容器,真正可挂载的是其content文档片段 - 多实例时,每个克隆都应独立处理
data-bind绑定,禁止全局正则替换{{title}}——易XSS,且无法支持属性级绑定(如data-bind="src")
id冲突必须用data-id动态生成
多个实例共用同一份<template></template>,硬编码id="modal"会导致document.getElementById('modal')永远只返回第一个匹配节点,JS逻辑必然错乱。
- 禁止写
id="submit"或id="dialog"这类泛化ID - 推荐方案:
data-id="${Date.now()}-${Math.random().toString(36).substr(2, 9)}",确保每次克隆唯一 - 若需DOM查询,统一用
rootEl.querySelector('[data-id="xxx"]'),不依赖getElementById
样式穿透必须靠attachShadow({ mode: 'closed' })
<template></template>本身不提供任何隔离能力,它只是浏览器跳过解析的静态占位符。所有class、style、script插入light DOM后,照样被主应用CSS穿透,Ant Design的.ant-btn依然生效。
- 关键动作:克隆后的fragment必须挂入
element.attachShadow({ mode: 'closed' })创建的shadowRoot,否则等于没建墙 - 错误认知:以为
data-app="cart"+ CSS前缀就能防穿透 —— 第三方库规则不认data属性,照样生效 -
mode: 'closed'比'open'更安全,但调试时需手动暴露接口,不能依赖element.shadowRoot直查
内联script和style必须剥离并重执行
模板里写<script>console.log('init')</script>,克隆后插入DOM,这段脚本根本不会执行;<style></style>若直接append到shadowRoot,会被当普通HTML解析,不进CSSOM。
立即学习“前端免费学习笔记(深入)”;
- 剥离
script标签,提取内容,在沙箱中用Function构造器执行,并显式绑定this、document到当前shadowRoot - 样式建议用
CSSStyleSheet实例+adoptedStyleSheets注入(Chrome 73+/Safari 16.4+),避免shadowRoot.innerHTML = '<style>...'</style> - 第三方CSS资源(如
<link rel="stylesheet">)需主应用拦截并重写为带scope的CSS文本,再注入
template当“模块”用却忘了它连作用域边界都没有——它既不隔离样式,也不隔离JS,甚至不保留状态。所有隔离效果,全靠你后续那一句attachShadow和配套的克隆、注入、绑定动作撑起来。



















