前端开发中,动态列表生成是常见需求,但稍有不慎就容易引发跨站脚本攻击(XSS)或性能瓶颈。以下是经过验证的实践原则:使用 createElement 配合 textContent 防范注入,模板字符串仅作为结构骨架;优先采用 template 标签加 cloneNode(true) 实现列表复用;高
HTML模板实现列表项动态生成详解
时间:2026-07-05
浏览:171
文章目录
前端开发中,动态列表生成是常见需求,但稍有不慎就容易引发跨站脚本攻击(XSS)或性能瓶颈。以下是经过验证的实践原则:使用 createElement 配合 textContent 防范注入,模板字符串仅作为结构骨架;优先采用 template 标签加 cloneNode(true) 实现列表复用;高频渲染时利用 DocumentFragment 批量插入节点;若需模拟响应式列表,一个 renderList 函数进行全量重绘即可兼顾性能与简洁。

模板字符串里怎么插变量才不会被当成纯文本
直接使用 innerHTML 拼接字符串时,一旦变量包含 < 或 ",DOM 结构就会被破坏,严重时甚至导致 XSS 漏洞。因此必须先转义再插入,或采用更安全的方案。
推荐组合 document.createElement 与 textContent:每个字段通过 textContent 单独赋值,天然规避注入风险。若确实需要渲染富文本,应先用 DOMPurify.sanitize() 过滤后再交给 innerHTML。
- 避免:
item.innerHTML = `—— 若- ${name}
`name来自用户输入,等于为攻击者留下后门 - 正确做法:
const li = document.createElement('li'); li.textContent = name; - 模板字符串仅用于搭建结构骨架,不嵌入变量:
const template = `
template 标签和 innerHTML 哪个更适合列表批量渲染
是浏览器原生支持的“离线” DOM 容器,其内部内容不渲染、不执行脚本、不加载资源,非常适合作为可复用的列表项模板。相比拼接字符串再塞入 innerHTML,它更可靠,尤其在需要绑定事件或维持子节点状态时。
需注意: 的内容必须通过 content.cloneNode(true) 复制出来,直接 appendChild 只会移动节点而非复制。
- 定义模板:
- 获取模板内容:
const tmpl = document.getElementById('item-template').content; - 填充并追加:
const el = tmpl.cloneNode(true); el.querySelector('.title').textContent = item.title; list.appendChild(el); - 务必传递
true参数,否则克隆的仅是外层节点自身
循环渲染时如何避免重复创建 DOM 节点
每次新增时调用 createElement 或克隆 本身并无问题,但高频操作(如滚动加载上千条数据)容易导致页面卡顿。关键在于“减少触发重排重绘”而非“减少创建”。
将全部新节点先放入 DocumentFragment,最后一次性 append 到目标容器,能显著减少 layout 回流次数。
- 错误示范:
items.forEach(item => list.appendChild(createItem(item)));—— 每次 append 都可能引发重排 - 正确做法:
const frag = document.createDocumentFragment(); items.forEach(item => frag.appendChild(createItem(item))); list.appendChild(frag); - 若使用
,同样先将克隆节点加入frag,最后挂载
Vue/React 风格的响应式列表在原生 JS 里怎么模拟
原生 JavaScript 缺少响应式数据绑定,但可通过 Proxy 监听数组变化,配合模板自动更新 DOM。需注意 push、splice 等方法不会触发 set,需重写它们或使用 Reflect.set 加手动通知。
更务实的方式是封装一个 renderList 函数,接收数据数组与渲染函数,每次数据变化时执行全量重绘——只要列表项不超过几百个,性能完全足够。
- 简单封装:
function renderList(container, data, renderItem) { container.innerHTML = ''; data.forEach(item => container.appendChild(renderItem(item))); } - 调用时传入编译好的节点:
renderList(listEl, items, item => createItem(item)); - 避免监听
Array.prototype.push—— 改写原型易引发兼容问题,且无法捕获字面量赋值
实际项目中,最容易忽略的是模板节点的复用边界。 适用于结构固定、字段较少的列表;一旦涉及条件分支(如不同状态显示不同子组件)、嵌套列表或复杂交互,手写模板很快就会失控——此时应考虑使用轻量框架或自定义 HTML 元素。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。