创建含多个子元素div的最优方案:createElement与innerHTML对比
安全认知补充
首先你的判断基本符合逻辑,但存在小缺陷:即便输入不是C端用户生成内容,如果输入来源包含运营后台配置、第三方接口返回、后端可编辑的配置项,依然有可能存在特殊字符或者被污染的风险,建议对插入模板的字符串做基础HTML转义,避免HTML结构被意外破坏。
另外你提供的第二个createDiv函数存在代码错误:模板字符串中直接使用了未声明的routing变量,运行时会直接抛出ReferenceError,正确写法是将该部分替换为${entry.routing ?? ''}。
问题逐一解答
1. 两种实现方案哪一种运行效率更高?
绝大多数场景下innerHTML方案性能更高,尤其是子元素结构复杂、批量生成大量节点时,差异更明显:
- 浏览器的HTML解析器是C++实现的,
innerHTML只需要一次JS到DOM引擎的上下文切换,就能一次性完成所有子节点的解析和创建 - 多次调用
createElement、appendChild会产生多次JS和DOM引擎的上下文切换开销,结构越复杂开销累积越明显 - 仅当子元素数量极少(1-2个)时,两者性能差异在微秒级,几乎感知不到。
2. 业界实际开发中更推荐使用哪一种?
没有统一答案,完全按需选择:
- 如果渲染完成后需要持有子元素引用做后续操作(绑定事件、动态修改属性、增删节点),更推荐
createElement + appendChild方案,创建时就能直接持有引用,不需要额外做DOM查询。 - 如果是纯静态渲染、结构固定、输入完全可控,更推荐
innerHTML方案,代码更简洁易读,修改结构的成本更低。
3. 两种方案分别还有哪些需要注意的潜在问题?
createElement + appendChild 方案
- 代码冗余度高,结构复杂时编写和维护成本极高,修改结构需要调整大量代码
- 代码结构和最终DOM结构对应性弱,很难一眼从代码判断最终渲染效果
innerHTML 方案
- 没有内置转义机制,输入中如果包含
<、>、"、'、&等特殊字符会直接破坏HTML结构,极端情况会引发注入风险 - 渲染完成后如果需要获取子元素引用,需要额外调用
querySelector等DOM查询API,额外增加开销 - 无法在创建节点的同时直接绑定事件,需要后续查询绑定或者用事件委托实现。
4. 是否存在比上述两种方案更高效、更合理的实现方式?
有两种纯原生的优化方案可以参考:
方案1:转义函数+模板字符串
封装一个极简的HTML转义函数,对所有插入模板的变量做转义,兼顾innerHTML的简洁性和安全性:
function escapeHtml(str) { return str.replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char])) } // 改进后的createDiv function createDiv (entry) { const div = document.createElement('div') div.classList.add('exchange') div.id = entry.exchange div.innerHTML = ` <img class="logo-image" src="/static/img/${escapeHtml(entry.img)}.png" alt="${escapeHtml(entry.name)}"> <a class="exchange-name" href="${escapeHtml(entry.url)}">${escapeHtml(entry.name)}</a> <span>${escapeHtml(entry.routing ?? '')}</span> <span class="price">${escapeHtml(entry.price)}</span> ` return div }
方案2:通用DOM创建工具函数封装
如果确实需要持有子元素引用,可以把公共属性赋值逻辑抽成工具函数,减少冗余代码:
function el(tag, props = {}, children = []) { const element = document.createElement(tag) Object.entries(props).forEach(([k, v]) => { if (k === 'class') element.className = v else if (k.startsWith('on')) element.addEventListener(k.slice(2).toLowerCase(), v) else element[k] = v }) children.forEach(child => element.append(typeof child === 'string' ? document.createTextNode(child) : child)) return element } // 改进后的createDiv function createDiv(entry) { return el('div', { class: 'exchange', id: entry.exchange }, [ el('img', { class: 'logo-image', src: `/static/img/${entry.img}.png`, alt: entry.name }), el('a', { class: 'exchange-name', href: entry.url }, [entry.name]), el('span', {}, [entry.routing ?? '']), el('span', { class: 'price' }, [entry.price]) ]) }
这个方案既保留了DOM API的安全性和直接操作节点的能力,又让代码结构和最终DOM结构一一对应,可读性更高。
内容的提问来源于stack exchange,提问作者lenny
相关产品推荐
相关产品推荐

