JavaScript中如何追加多个项目

52

我有以下函数,尝试找到更好的方式使用appendChild()添加多个项。

当用户点击“添加”时,每个项应该像这样:

<li>
  <input type="checkbox">
  <label>Content typed by the user</label>
  <input type="text">
  <button class="edit">Edit</button>
  <button class="delete">Delete</button>
</li>

我有这个函数来添加这些元素:

function addNewItem(listElement, itemInput) {
  var listItem = document.createElement("li");
  var listItemCheckbox = document.createElement("input");
  var listItemLabel = document.createElement("label");
  var editableInput = document.createElement("input");
  var editButton = document.createElement("button");
  var deleteButton = document.createElement("button");

  // define types
  listItemCheckbox.type = "checkbox";
  editableInput.type = "text";

  // define content and class for buttons
  editButton.innerText = "Edit";
  editButton.className = "edit";
  deleteButton.innerText = "Delete";
  deleteButton.className = "delete";

  listItemLabel.innerText = itemText.value;

  // appendChild() - append these items to the li
  listElement.appendChild(listItem);
  listItem.appendChild(listItemCheckbox);
  listItem.appendChild(listItemLabel);
  listItem.appendChild(editButton);
  listItem.appendChild(deleteButton);

  if (itemText.value.length > 0) {
    itemText.value = "";
    inputFocus(itemText);
  }
}

但是您可能会注意到我重复了三次appendChild(),以添加listItem。是否可以一次性添加多个项目到appendChild()中?


可能是https://dev59.com/VmHVa4cB1Zd3GeqPlEIE的重复问题。 - Shakespeare
15个回答

109

您可以使用DocumentFragment完成它。

var documentFragment = document.createDocumentFragment();
documentFragment.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
listElement.appendChild(documentFragment);

使用DocumentFragments可以将子元素放置在类似节点的父级上,实现类似节点的交互,而无需真正的根节点。这样做可以在不在可见DOM中创建结构。


14
但是所有这些 appendChild 调用都不会在实时 DOM 中发生,因此浏览器不会尝试在每个子节点被添加后重新渲染整个 DOM。相反,在整个片段被添加后只会发生一次重新渲染。 - rodrigo-silveira
6
好的回答-DocumentFragment是一种非常有用的方式,可以批处理对渲染DOM的更改。 - And Finally
4
这应该成为被接受的答案,因为它使用DOM API而不是字符串。非常棒的答案。谢谢。 - 3Dos
2
Mozilla文档:Document.createDocumentFragment() - GuoJunjun
哇,谢谢!这正是我在寻找的! - 010011100101

62
你可以在JavaScript中使用append方法。
这类似于jQuery的append方法,但不支持IE和Edge。
你可以更改此代码。
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
listElement.append(listItem,listItemCheckbox,listItemLabel,editButton,deleteButton);

1
这太棒了,但太遗憾IE仍不支持它。 - Crismogram
1
截至2019年1月,append得到广泛支持,但它并未标准化,被视为实验性技术。因此应谨慎使用。 - skzryzg
3
@skzryzg,从mdn上看,我认为它不再是实验性的了。 - Timo

14

就我个人而言,我不明白你为什么要这样做。

但是如果你真的需要用一条语句替换所有的appendChild(),你可以将创建的元素的outerHTML分配给li元素的innerHTML

你只需要替换以下内容:

  listElement.appendChild(listItem);
  listItem.appendChild(listItemCheckbox);
  listItem.appendChild(listItemLabel);
  listItem.appendChild(editButton);
  listItem.appendChild(deleteButton);

以下是:

listItem.innerHTML+= listItemCheckbox.outerHTML + listItemLabel.outerHTML + editButton.outerHTML + deleteButton.outerHTML;
listElement.appendChild(listItem);

解释:

outerHTML 属性是元素DOM接口的一个属性,它获取了描述该元素及其后代的序列化HTML片段。因此将创建的元素的 outerHTML 分配给 li 元素的 innerHTML,类似于将它们附加到其中。


3
这实际上并没有将子元素添加到其中,而是添加了它们的副本,这会导致属性丢失。 - Michael Scott Asato Cuthbert
@MichaelScottCuthbert 哪些属性会丢失?我不明白。它已经做了应该做的事情。 - cнŝdk
2
设置属性,例如listCheckbox.asdf = 5; listItem.innerHTML = listCheckbox.outerHTML; listItem.querySelector('input').asdf === 5,结果将会是错误的,因为属性(不同于属性)在outerHTML中无法找到。对于输入项来说,最重要的丢失属性是.value(与属性"value"不同)。还有事件监听器等。对于许多目的(包括此处),您的解决方案可以工作,但它确实会丢失属性并且速度较慢。 - Michael Scott Asato Cuthbert
@MichaelScottCuthbert 是的,我明白你的意思,你是正确的,但是我所指的只是需要处理HTML对象树。 - cнŝdk
我认为总体而言应该避免使用innerHTML。如果您注册任何事件监听器或引用DOM元素,则使用innerHTML会导致所有这些内容丢失,并且这可能会在将来导致应用程序出现意外行为。 - undefined
当然可以,我的回答只是为了快速解决OP的请求。 - cнŝdk

13

合并 @Atrahasis 和 @Slavik 的回答:

if (Node.prototype.appendChildren === undefined) {
  Node.prototype.appendChildren = function() {
    let children = [...arguments];

    if (
      children.length == 1 &&
      Object.prototype.toString.call(children[0]) === "[object Array]"
    ) {
      children = children[0];
    }

    const documentFragment = document.createDocumentFragment();
    children.forEach(c => documentFragment.appendChild(c));
    this.appendChild(documentFragment);
  };
}

这个函数可以接受多个子元素作为参数,或者作为一个数组参数传递:

foo.appendChildren(bar1, bar2, bar3);
bar.appendChildren([bar1, bar2, bar3]);

2020年6月更新

当前大多数浏览器现在都支持append"展开语法"

上述调用可以重写为:

foo.append(bar1, bar2, bar3);
bar.append(...[bar1, bar2, bar3]);

1
P.S. 仅 ES6+,我肯定这段代码可以以更好的方式编写。将节点展开成数组/仅接受一个数组作为单个参数代码路径(主要是为了使用forEach)可能非常糟糕。此外,向Node.prototype/similar添加函数可能不是您应该做的事情。 - Pend
我在一个存在条件中包装了polyfill。 我认为将其添加到Node中是可以的,因为Node是定义appendChild的对象。 虽然Sagar V的回答是最好的方法,特别是使用扩展运算符的支持。 我在您的答案末尾添加了一个更新版本。 - Mr. Polywhirl

7

让我们尝试一下:

let parentNode = document.createElement('div');

parentNode.append(...[
    document.createElement('div'),
    document.createElement('div'),
    document.createElement('div'),
    document.createElement('div'),
    document.createElement('div')
]);

console.log(parentNode);

5
你需要添加多个子元素?只需使用 appendChildren 将其变为复数形式!
首先:
HTMLLIElement.prototype.appendChildren = function () {

  for ( var i = 0 ; i < arguments.length ; i++ )

    this.appendChild( arguments[ i ] );

};

对于任何列表元素:

listElement.appendChildren( a, b, c, ... );

//check :
listElement.childNodes;//a, b, c, ...

当然,它可以与每个具有appendChild方法的元素一起使用!例如HTMLDivElement


将这个与Slavik答案中的DocumentFragment结合起来,可以避免多次appendChild调用主DOM。 - And Finally
@AndFinally - 真的很好奇/不确定:为什么?重复添加到片段比DOM在操作上更便宜吗? - Jailbot
2
@RockinSocks 将一系列节点附加到片段中,并在所有内容都组合好后仅附加片段,可以避免浏览器对页面上元素的位置和大小进行多次重新计算,即进行多次回流。这样,当您将片段附加到实时DOM时,只会发生一次回流。因此,更改应该呈现得更快。请参见此处以获取快速说明-https://www.sitepoint.com/10-ways-minimize-reflows-improve-performance/. - And Finally
@AndFinally 这对现代浏览器是否真实有效呢?我想浏览器会足够聪明,如果在单个执行中发生一系列DOM更改,则保持更新。 - Max Yari

5
你可以使用 createContextualFragment,它会返回一个由字符串创建的 documentFragment。 如果你需要一次性构建和添加多个 Node 到现有的 Element 中,这是非常完美的,因为你可以在没有 innerHTML 的缺点的情况下全部添加。

https://developer.mozilla.org/en-US/docs/Web/API/Range/createContextualFragment

// ...
var listItem = document.createElement("li");
var documentFragment = document.createRange().createContextualFragment(`
    <input type="checkbox">
    <label>Content typed by the user</label>
    <input type="text">
    <button class="edit">Edit</button>
    <button class="delete">Delete</button>
`)
listItem.appendChild(documentFragment)
// ...

3

为什么没有人提到 element.append() 函数呢?!

你可以简单地使用它来依次添加多个项目,如下所示:

listItem.append(listItemCheckbox, listItemLabel, editButton, deleteButton);


3
您可以将元素分组成一个单独的innerHTML组,方法如下:
  let node = document.createElement('li');
  node.innerHTML = '<input type="checkbox"><label>Content typed by the user</label>  <input type="text"><button class="edit">Edit</button><button class="delete">Delete</button>';
  document.getElementById('orderedList').appendChild(node);

那么appendChild()仅被使用一次。


请注意,这样做将删除附加节点上附加的任何事件侦听器,因为innerHTML不会像appendChild一样保留这些内容。 - ojathelonius
@ojathelonius,那是无关紧要的:节点在这里被创建,因此无论如何它都没有任何事件监听器。 - trincot
我说的不是在这里创建的节点,而是它的内容。innerHTML 不会保留任何附加的事件监听器。 - ojathelonius

2

如果您使用内置的arguments对象,则可以编写自己的函数。

function appendMultipleNodes(){
  var args = [].slice.call(arguments);
  for (var x = 1; x < args.length; x++){
      args[0].appendChild(args[x])
  }
  return args[0]
}

那么你需要这样调用函数:
appendMultipleNodes(parent, nodeOne, nodeTwo, nodeThree)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接