You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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树校验,数据量较大时会触发多次页面重排,依然不是最优解。

批量插入场景下的性能从高到低排序为:

  1. 内存中拼接完整HTML字符串,循环结束后一次性赋值给父容器innerHTML
  2. 用DocumentFragment离线组装所有createElement生成的节点,组装完成后一次性插入真实DOM
  3. 循环内逐个createElement直接append到真实DOM
  4. 循环内每次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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 16:24:19