<template> 不触发渲染或执行脚本,需手动克隆并插入文档才能运行;内联 <script> 和 <link> 均不会自动执行或加载,须显式处理;动态加载应通过 createElement('template') + innerHTML 实现;延迟执行依赖 JS 控制挂载与初始化时机;推荐用 customElements.define() 替代手工模板管理。

HTML 模板中 <template> 本身不触发渲染或执行
直接把带 <script> 的 HTML 片段塞进 <template>,脚本不会自动运行——这是最常被误解的一点。浏览器解析 <template> 时会挂起其内容的 DOM 构建和 JS 执行,直到你显式调用 .content.cloneNode(true) 并插入文档。
所以「动态加载」不是靠模板自动完成的,而是靠你控制克隆、挂载、执行时机。常见错误是:写好 <template id="modal"><div>...</div><script>initModal()</script></template>,然后只做 document.getElementById('modal').content,结果 initModal 根本没调。
- 所有内联
<script>在克隆后仍处于“未执行”状态,需手动eval()或重新注入<script>标签(注意 CSP 限制) -
<link rel="stylesheet">同样不会自动加载,得手动创建<link>并 append 到<head> - 若模板含自定义元素(如
<my-button>),需确保其定义已在克隆前注册,否则会降级为HTMLUnknownElement
用 document.createElement('template') + innerHTML 动态生成模板更灵活
硬编码 <template> 标签适合静态组件,但真正「动态加载」往往来自远程 HTML 字符串(比如从 API 拿到的组件片段)。这时不能依赖预置 id,而要运行时创建模板:
const tpl = document.createElement('template');
tpl.innerHTML = await fetch('/components/chart.html').then(r => r.text());
const frag = tpl.content.cloneNode(true);
document.body.appendChild(frag);
注意:如果返回的 HTML 含 <script src="chart.js">,它不会自动下载执行;必须遍历 frag.querySelectorAll('script'),对每个 src 创建新 <script> 并 append,或对内联脚本用 Function(script.textContent)()(仅限开发环境,生产慎用)。
立即学习“前端免费学习笔记(深入)”;
- 使用
innerHTML设置模板内容时,浏览器会解析并丢弃无效标签(如孤立</div>),但不会报错,容易掩盖模板格式问题 - 若服务端返回的是纯 JSON 而非 HTML 字符串,别试图用
innerHTML = JSON.stringify(...),应先用模板字符串或DOMParser构建合法片段
延迟执行的关键是分离「挂载」与「初始化」
所谓「延迟执行」,本质是把组件逻辑的触发时机从 DOM 插入时刻,推迟到某个条件满足之后(如用户滚动到视口、点击按钮、或资源加载完成)。模板本身不提供延迟能力,得靠 JS 控制流程:
const tpl = document.querySelector('#card-tpl');
const card = tpl.content.cloneNode(true);
// 先不插入 DOM
let mounted = false;
const mount = () => {
if (!mounted) {
document.querySelector('#container').appendChild(card);
mounted = true;
// 此时才执行初始化逻辑
card.querySelector('button').addEventListener('click', handleCardClick);
}
};
// 延迟触发 mount()
window.addEventListener('scroll', () => {
if (isInViewport(card)) mount();
});
- 不要在克隆后立刻调用
querySelector等方法操作card,因为此时它还没挂载,offsetTop、getBoundingClientRect()都不可靠 - 若组件依赖 CSS 动画或伪类(如
:hover),必须等挂载后再触发重排(例如card.offsetHeight),否则动画可能不触发 - 多次调用
mount()不应重复添加事件或重复请求数据,加mounted标志是最简单防重手段
现代替代方案:用 customElements.define() 替代手工模板管理
如果你频繁处理「模板 → 克隆 → 挂载 → 初始化」这套流程,说明你其实在重复实现 Web Components 的基础能力。直接定义自定义元素,能天然解决动态加载、延迟实例化、作用域隔离等问题:
class LazyChart extends HTMLElement {
async connectedCallback() {
if (this.rendered) return;
const html = await fetch('/components/chart.html').then(r => r.text());
this.innerHTML = html;
// 此时 this 已挂载,可安全操作 DOM 和发起请求
this.initChart();
this.rendered = true;
}
}
customElements.define('lazy-chart', LazyChart);
使用时:<lazy-chart data-id="123"></lazy-chart>,浏览器会在元素进入 DOM 时自动调用 connectedCallback。比手动管理模板更可靠,也规避了 CSP 对 eval 的拦截风险。
不过要注意:自定义元素的 constructor 中不能访问 this.innerHTML 或 this.children,因为此时 DOM 还未解析完成;所有读取属性、初始化逻辑必须放在 connectedCallback 或 attributeChangedCallback 中。



















