JavaScript动态向HTML添加元素的最高效方式对比
JavaScript动态批量添加DOM元素的最优实现方案(用户列表场景)
需求背景
需要通过JS动态向页面批量插入用户列表条目,目标生成的DOM结构如下:
<div id="user-list" style="display:flex; flex-direction:column"> <div id="user-1"> <span class="photo" style="width:25%">PHOTO</span> <span class="name" style="width:25%">name</span> <span class="surname" style="width:25%">surname</span> <span class="age" style="width:25%">age</span> </div> <div id="user-2"> <span class="photo" style="width:25%">PHOTO</span> <span class="name" style="width:25%">name</span> <span class="surname" style="width:25%">surname</span> <span class="age" style="width:25%">age</span> </div> </div>
你当前测试的两种实现
方案1:循环内通过innerHTML += 追加节点
// 注意:这里属性名有拼写错误,正确写法是innerHTML,不存在innerHtml属性 document.getElementById("user-list").innerHtml += `<div id="user-${userId}"> <span class="photo" style="width:25%">${userPhoto}</span> <span class="name" style="width:25%">${userName}</span> <span class="surname" style="width:25%">${userSurname}</span> <span class="age" style="width:25%">${userAge}</span> </div>`
方案2:循环内逐个createElement后appendChild追加
var user = document.createElement("div"); user.id = `user-${userId}` var photo = document.createElement("span"); photo.setAttribute('style','width:25%') photo.innerHTML = userPhoto; var name = document.createElement("span"); name.setAttribute('style','width:25%') name.innerHTML = userName; var surname = document.createElement("span"); surname.setAttribute('style','width:25%') // 注意:这里赋值错误,把surname内容写成了userName surname.innerHTML = userName; var age = document.createElement("span"); age.setAttribute('style','width:25%') age.innerHTML = userAge; user.appendChild(photo); user.appendChild(name); user.appendChild(surname); user.appendChild(age); document.getElementById("user-list").appendChild(user);
效率对比结论
两种写法在一次性添加20条数据的小量级下,用户感知不到明显差异,但从实现原理和大数据量场景的表现来看,性能差距非常明显:
- 你当前写的
innerHTML +=写法是性能最差的实现:这个操作本质不是“追加新内容”,而是每次执行时先把父容器内所有现有DOM序列化成HTML字符串,拼接上新的片段后,再把整个大字符串重新解析成DOM,全量替换父容器原有内容。这个过程会丢失所有子元素上已绑定的事件、持有的DOM引用,重复序列化+解析的开销会随着容器内子节点数量增加线性上涨。 - 你当前写的逐个createElement+循环内直接append到真实DOM的写法,性能比上面的
innerHTML +=好,但每次调用appendChild操作真实DOM都会触发浏览器的DOM树校验,数据量较大时会触发多次页面重排,依然不是最优解。
批量插入场景下的性能从高到低排序为:
- 内存中拼接完整HTML字符串,循环结束后一次性赋值给父容器
innerHTML - 用
DocumentFragment离线组装所有createElement生成的节点,组装完成后一次性插入真实DOM - 循环内逐个createElement直接append到真实DOM
- 循环内每次
innerHTML +=追加内容(不推荐使用)
最优实现参考
根据你的场景二选一即可:
场景1:插入纯静态内容、无事件绑定需求、内容可信
这种场景优先选一次性拼接innerHTML的方案,性能最高:
const userList = document.getElementById("user-list"); let htmlBuffer = ''; // 所有拼接操作在内存中完成,不操作真实DOM for (const user of userDataList) { htmlBuffer += `<div id="user-${user.id}"> <span class="photo" style="width:25%">${user.photo}</span> <span class="name" style="width:25%">${user.name}</span> <span class="surname" style="width:25%">${user.surname}</span> <span class="age" style="width:25%">${user.age}</span> </div>`; } // 一次性插入,仅触发一次HTML解析、一次页面重排 userList.innerHTML = htmlBuffer;
注意:如果插入的内容包含用户输入的不可信数据,不要用该方案,避免XSS注入风险。
场景2:需要给子节点绑定事件、插入内容不可信
这种场景优先选DocumentFragment离线组装方案,兼顾性能和安全性:
const userList = document.getElementById("user-list"); // 创建离线文档碎片,操作碎片不会触发真实DOM更新和页面重排 const fragment = document.createDocumentFragment(); for (const userData of userDataList) { const userEl = document.createElement("div"); userEl.id = `user-${userData.id}`; const photoEl = document.createElement("span"); photoEl.className = 'photo'; photoEl.style.width = '25%'; // 纯文本内容用textContent赋值,比innerHTML更安全、性能更好 photoEl.textContent = userData.photo; const nameEl = document.createElement("span"); nameEl.className = 'name'; nameEl.style.width = '25%'; nameEl.textContent = userData.name; const surnameEl = document.createElement("span"); surnameEl.className = 'surname'; surnameEl.style.width = '25%'; surnameEl.textContent = userData.surname; const ageEl = document.createElement("span"); ageEl.className = 'age'; ageEl.style.width = '25%'; ageEl.textContent = userData.age; userEl.append(photoEl, nameEl, surnameEl, ageEl); // 节点先追加到离线碎片 fragment.appendChild(userEl); } // 所有节点组装完成后一次性插入真实DOM,仅触发一次重排 userList.appendChild(fragment);
原有代码的细节问题
- 方案1中DOM属性拼写错误:标准属性名是
innerHTML,不存在innerHtml这个属性,写了不会生效。 - 方案2中姓氏节点的内容赋值错误,误将
userSurname写成了userName,会导致内容显示错误。 - 给元素设置单个样式时,直接用
element.style.xxx = 'xxx'的写法比setAttribute('style', ...)更合适,后者会覆盖元素上已有的所有行内样式。 - 纯文本内容优先用
textContent赋值,不需要经过HTML解析,性能更好,也能避免XSS风险。
内容的提问来源于stack exchange,提问作者Kjut Nikolas
相关产品推荐
相关产品推荐

