原文链接:https://javascript.info/modifying-document,translate with ❤️ by zhangbao.

DOM 修改是创建“动态”网页的关键。

本章,我们将看到如何“动态地”创建新元素,并修改现有的页面内容。

我们先看一个简单的示例,然后再解释。

例子:显示一个消息框

我们先写一个比原声 alert 好看的弹框。

  1. <style>
  2. .alert {
  3. border: 1px solid #d6e9c6;
  4. border-radius: 4px;
  5. padding: 15px;
  6. color: #3c763d;
  7. background-color: #dff0d8;
  8. }
  9. </style>
  10. <div class="alert">
  11. <strong>Hi, 看这里!</strong> 这是一条很重要的信息。
  12. </div>

这个 <div> 是我们手写出来的,下面用 JavaScript 来创建它。

创建元素

创建 DOM 节点的方法有两个:

document.createElement(tag)

创建指定标签名的元素:

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

document.createTextNode(text)``

创建指定文本的元素:

  1. let textNode = document.createTextNode('我在这里');

创建消息框

现在我们创建一个 div,给它类名和文本内容:

  1. let div = document.createElement('div');
  2. div.className = 'alert alert-success';
  3. div.innerHTML = '<strong>嗨,看这里!</strong> 这是一条很重要的信息。'

现在,我们已经有了一个准备好的 DOM 元素了。就保存在变量 div 中,但是还看不见,因为还没有插入到页面。

插入方法

为了能看到我们创建的 <div> 元素,下面我们要将它插入到 document 中的某处。例如,插入到 document.body 中。

有一个特别的方法:document.body.appendChild(div)

下面是完整代码:

  1. <style>
  2. .alert {
  3. border: 1px solid #d6e9c6;
  4. border-radius: 4px;
  5. padding: 15px;
  6. color: #3c763d;
  7. background-color: #dff0d8;
  8. }
  9. </style>
  10. <script>
  11. let div = document.createElement('div');
  12. div.className = 'alert alert-success';
  13. div.innerHTML = '<strong>嗨,看这里!</strong> 这是一条很重要的信息。';
  14. document.body.appendChild(div);
  15. </script>

下面列举了向父元素插入节点的方法列表(parentElem 表示父节点):

parentElem.appendChild(node)

node 作为最后一个孩子节点插入到 parentElem 中。

下面例子中,在 <ol> 的结尾插入了一个新的 <li>

  1. <ol id="list">
  2. <li>0</li>
  3. <li>1</li>
  4. <li>2</li>
  5. </ol>
  6. <script>
  7. let newLi = document.createElement('li');
  8. newLi.innerHTML = '你好,世界!';
  9. list.appendChild(newLi);
  10. </script>

parentElem.insertBefore(node, nextSibling)

可解释为”insert node before nextSibling“,node 是我们插入的节点,nextSiblingparentElem 中已存在的节点,我们要做的就是在 nextSibling 之前插入 node

下面的例子,在第二个 <li> 之前插入新的一个 <li>

  1. <ol id="list">
  2. <li>0</li>
  3. <li>1</li>
  4. <li>2</li>
  5. </ol>
  6. <script>
  7. let newLi = document.createElement('li');
  8. newLi.innerHTML = '你好,世界!';
  9. list.insertBefore(newLi, list.children[1]);
  10. </script>

newLi 作为第一个元素插入到 list 中:

  1. list.insertBefore(newLi, list.firstChild);

parentElem.replaceChild(node, oldChild)``

可解释为”replace oldChild with node“。

这些方法都返回被插入的节点。就是说,parentElem.appendChild(node) 返回 node。但通常返回值不被使用,我们只是为了使用这些插入方法而已。

这些方法都是“老派”:它们存在于很久之前,我们可以用许多旧的脚本中看到它们。不幸的是,有些任务很难用它们来解决。

例如,我们如何将字符串作为html插入?给了一个节点,怎样在这个节点之前 插入另一个节点。当然,所有这些都是可行的,但并不优雅。

因此,还有另外两套插入方法可以轻松处理所有的情况。

prepend/append/before/after

这些方法提供了更加灵活的插入方式:

  • node.append(...nodes or strings):在 node 的结尾附加节点/字符串。

  • node.prepend(...nodes or strings): 在 node 的开头插入节点/字符串。

  • node.before(...nodes or strings): 在 node 节点之前插入节点/字符串。

  • node.after(...nodes or strings):在 node 节点之后插入节点/字符串。

  • node.replaceWith(...nodes or strings):将 node 替换为我们给的节点/字符串。

以下是使用这些方法向列表中添加更多项目以及在其之前/之后添加文本的示例:

  1. <ol id="ol">
  2. <li>0</li>
  3. <li>1</li>
  4. <li>2</li>
  5. </ol>
  6. <script>
  7. ol.before('before');
  8. ol.after('after');
  9. let prepend = document.createElement('li');
  10. prepend.innerHTML = 'prepend';
  11. ol.prepend(prepend);
  12. let append = document.createElement('li');
  13. append.innerHTML = 'append';
  14. ol.append(append);
  15. </script>

结果如下:

修改文档 - 图1

下面这张图介绍,这些方法作用的位置:

修改文档 - 图2

最终的列表代码是这样的:

  1. before<ol id="ol"><li>prepend</li>
  2. <li>0</li>
  3. <li>1</li>
  4. <li>2</li>
  5. <li>append</li></ol>after

这些方法还可以一次同时插入多个节点或者文本片段。

例如,下面我们插入了一段文本和一个元素:

  1. <div id="div"></div>
  2. <script>
  3. div.before('<p>Hello</p>', document.createElement('hr'));
  4. </script>

字符串都会当作纯文本内容插入。

所以脚本执行后,最终 HTML 代码如下:

  1. &lt;p&gt;Hello&lt;/p&gt;<hr><div id="div"></div>

也就是说,字符串内容会被安全插入,如同 elem.textContent 一样。

所以,这些方法只能用于插入 DOM 节点或者文本节点。

不过如果我们想把字符串当作 HTML 插入,如同 elem.innerHTML 一样,怎么办?

insertAdjacentHTML/Text/Element

还有另一个非常通用的方法:elem.insertAdjacentHTML(where, html)

这个方法的第一个参数是个字符串,表示插入的位置,可能的取值是:

  • "beforebegin":在 elem 之前插入 html

  • "afterbegin":将 html 作为 elem 的第一个孩子插入。

  • "beforeend":将 html 作为 elem 的最后一个孩子插入。

  • "afterend":在 elem 之后插入 html

小提示:可以把这里的 begin 看成是开始标签,把 end 看成是结束标签。

第二个参数是个字符串,表示要插入的 HTML 内容。

例如:

  1. <div id="div"></div>
  2. <script>
  3. div.insertAdjacentHTML('beforebegin', '<p>你好</p>');
  4. div.insertAdjacentHTML('afterend', '<p>再见</p>');
  5. </script>

运行结果如下:

  1. <p>你好</p><div id="div"></div><p>再见</p>

这就是我们如何在页面中附加任意的 HTML 的方法。

下面是描述 4 种插入位置的图示:

修改文档 - 图3

我们可以很容易地注意到这和前一幅图的相似之处。插入点实际上是相同的,但是这个的方法插入的是 HTML。

这个方法还有两个相似的兄弟方法:

  • elem.insertAdjacentText(where, text):相同的语法,不过插入的是文本。

  • elem.insertAdacentElement(where, text):相同的语法,不过插入的是元素。

它们存在主要是为了使语法“统一”。在实践中,大多数情况下只使用 insertAdjacentHTML,因为对于元素和文本,我们已经有了方法 append/prepend/before/after ——它们更短,并且可以插入节点/文本块。

这是另一种显示消息框的方案:

  1. document.body.insertAdjacentHTML('afterbegin',
  2. `<div class="alert alert-success">
  3. <strong>嗨,看这里!</strong> 这是一条很重要的信息。
  4. </div>`);

克隆节点:cloneNode

怎样去多插入一个类似的消息框呢?

我们可以把显示消息框的代码封装在一个函数里面。还有一个可选的方案是克隆已存在的 div 元素,然后修改它的内容(需要的话)。

有时候我们有一个很大的元素,用克隆的方式可能更快和简单。

  • elem.cloneNode(true):“深度”克隆。元素 elem 的所有特性和所有子元素都被克隆。

  • elem.cloneNode():等同于 elem.cloneNode(false),只克隆元素本身,不包含子元素。

下面举例子:

  1. <div class="alert" id="div">
  2. <strong>嗨,看这里!</strong> 这是一条很重要的信息。
  3. </div>
  4. <script>
  5. let div2 = div.cloneNode(true); // 深度克隆 div
  6. div2.querySelector('strong').innerHTML = '再见!'; // 改变文本
  7. div.after(div2); // 在已经存在的 div 之后显示克隆元素
  8. </script>

删除方法

删除节点,可以使用下列方法:

parentElem.removeChild(node)

删除 parentElem 的孩子节点 node

node.remove()

删除 node 节点。

第二个方法更简短,第一个方法为了兼容,仍然存在。

修改文档 - 图4请注意:

如果我们要移动一个元素到另外一个位置的话,不需要先删除它再移动。

因为对插入方法而言,如果插入的是一个已存在的节点,那么就是在移动这个节点。

例如,我们看个交换元素的例子:

  1. <div id="first">第一个</div>
  2. <div id="second">第二个</div>
  3. <script>
  4. // 无需删除,#first 就处于 #second 之后了
  5. second.after(first);
  6. </script>

我们再添加一个消息框,设定在 1 秒钟后删除:

  1. let div = document.createElement('div');
  2. div.className = 'alert alert-success';
  3. div.innerHTML = '<strong>嗨,看这里!</strong> 这是一条很重要的信息。';
  4. document.body.append(div);
  5. setTimeout(() => div.remove(), 1000);
  6. // 或者使用 setTimeout(() => document.body.removeChild(div), 1000);

一句话介绍 document.write

还有一种非常古老的方法,可以将东西添加到页面上:document.write

  1. <p>页面中的一些内容...</p>
  2. <script>
  3. document.write('<b>来自 JS 的问候</b>');
  4. </script>
  5. <p>结束</p>

结果如下:
修改文档 - 图5

调用 document.write(html) 会将字符串 html “立即写到当前页面的当前位置处”。html 字符串可以动态生成,所以它很灵活。我们可以使用 JavaScript 创建一个完整的网页并写入它。

这个方法来自于没有 DOM,没有标准的时代。真是旧时代,它现在仍然存在,是因为仍有脚本在使用它。

在现代脚本代码中,我们很少能看到它,是因为有以下几个重要的限制:

document.write`` **只在页面加载时才能工作。**

如果我们在事后调用它,那么现有的文档内容都会被删除。

例如:

  1. <p>1 秒钟后,这个页面里的内容都将被替换...</p>
  2. <script>
  3. // 1 秒钟后执行 document.write
  4. // 这是在页面加载完成之后,页面内已经存在的内容都会被新内容替换掉
  5. setTimeout(() => document.write('<b>...被这个替换掉了。</b>'), 1000);
  6. </script>

所以,与上面介绍的其他DOM方法不同的是,它在“加载后”阶段无法使用。

这是它的缺点。

从技术上讲,当调用 document.write,浏览器还在读取 HTML 的时候,它向页面中附加了一些内容,而浏览器会像最初那样使用它。

这给了我们一个好处——它运行得非常快,因为 没有 DOM 修改。它被直接写入页面文本,而此时 DOM 还没有构建,浏览器会在它执行的地方放置写入内容。

因此,如果我们需要动态地将大量的文本添加到 HTML 中,并且我们处于页面加载阶段,很重视速度的话,使用这个方法可能会有所帮助。但在实践中,很少有这样的场景。通常我们可以在脚本中看到这个方法,说明它是旧代码。

总结

创建新节点的方法:

  • document.createElement(tag):创建指定标签的元素节点。

  • document.createTextNode(value):创建文本节点(很少使用)。

  • elem.cloneNode(deep):克隆元素阶段,如果 deeptrue,所有子元素也会被克隆。

插入和删除节点:

  • 从父元素角度:

  • parent.appendChild(node)

  • parent.insertBefore(node, nextSibling)

  • parent.removeChild(node)

  • parent.replaceChild(newElem, node)

这些方法返回 node

  • 给定一个节点/字符串的列表

  • node.append(...nodes or strings):在节点尾部插入 node,

  • node.prepend(...nodes or strings):在节点头部插入 node,

  • node.before(...nodes or strings):在节点之前插入 node,

  • node.after(...nodes or strings):在节点之后插入 node,

  • node.replaceWith(...nodes or strings):替换 node,

  • node.remove():删除 node

字符串会被当作文本插入。

浏览器兼容性 😥:

  • remove:IE 浏览器不支持,mobile safair 不支持。

  • append/preappend:IE/Edge 浏览器不支持。

  • before/after/replaceWith:IE/Edge/Safair 浏览器不支持。

  • 给出一段 HTML 文本:使用 elem.insertAdjacent(where, html),插入到指定的位置:

  • "beforebegin":在 elem 之前插入 html,

  • "afterbegin":在 elem 的头部插入 html,

  • "beforeend": 在 elem 的尾部插入 html,

  • "afterend":在 elem 之后插入 html

还有两个类似的方法 elem.insertAdjacentTextelem.insertAdjacentElement,分别用来插入文本和元素,不过很少使用。

  • 在页面加载完成之前将 HTML 附加到页面上:

  • document.write(html)

在页面加载之后,调用这个会替换当前文档的全部内容。大部分都是在旧脚本里才能看到这个代码。

练习题

问题

一、creatTextNode vs innerHTML vs textContent

有一个空的 DOM 元素和字符串 text。

下面这 3 个命令哪些是在做一样的事情?

  1. elem.append(document.createTextNode(text))

  2. elem.innerHTML = text

  3. elem.textContent = text

二、清除元素

创建一个函数 clear(elem) 删除元素中的所有东西。

  1. <ol id="elem">
  2. <li>Hello</li>
  3. <li>World</li>
  4. </ol>
  5. <script>
  6. function clear(elem) { /* 你的代码 */ }
  7. clear(elem); // 清除列表
  8. </script>

三、为什么 “aaa” 没有被删除?

执行下面的例子。为什么 table.remove() 没有删除文本 "aaa"

  1. <table id="table">
  2. aaa
  3. <tr>
  4. <td>测试</td>
  5. </tr>
  6. </table>
  7. <script>
  8. alert(table); // [object HTMLTableElement]
  9. table.remove();
  10. // 为什么文档中还存在 aaa?
  11. </script>

四、创建列表

写一个页面,根据用户输入来创建列表。

针对每个列表项:

  1. 使用 prompt 询问用户内容。

  2. 使用用户输入的内容创建 <li>,并将其附加到 <ul> 中。

  3. 继续直到用户取消了输入(通过按下 Esc 按键或者 prompt 的取消按钮)。

所有的元素都应该是动态创建的。

如果用户输入了 HTML 标签,应该当成普通文本对待。

五、从一个对象中,创建一个 DOM 树

写一个函数 createTree,从一个嵌套对象来创建出对应的 DOM 树结构表示。

例如:

  1. let data = {
  2. "Fish": {
  3. "trout": {},
  4. "salmon": {}
  5. },
  6. "Tree": {
  7. "Huge": {
  8. "sequoia": {},
  9. "oak": {}
  10. },
  11. "Flowering": {
  12. "redbud": {},
  13. "magnolia": {}
  14. }
  15. }
  16. };

使用:

  1. let container = document.getElementById('container');
  2. createTree(container, data); // 在容器中创建树

结果看起来应该是这样的:

修改文档 - 图6

选择下列两种方式之一来解决这个任务:

  1. 创建树的 HTML 代码,然后赋值给 container.innerHTML

  2. 使用 DOM 的 append 方法来创建树节点。

要是两种方式都做出来了,就更好了。

P.S. 树结构中,不应该存在像 <ul></ul> 这样的空叶子“额外”元素。

六、展示 DOM 树的后代

有一个由内嵌 ul/li 组成的 DOM 树结构。

写代码,为每一个 <li> 标签的内容追加其后代元素个数。忽略叶子节点(没有孩子的节点)。

结果如下:

修改文档 - 图7

七、创建日历

写一个函数 createCalendar(elem, year, month)

调用这个函数的结果是创建指定年/月下的日历,并放入 elem 元素中。

例如应该是一张表格,一周用 <tr> 表示,一天用 <td> 表示。表格最顶部是表示周几的 <th> 元素:第一天是周一,一直到周日。

例如,createCalendar(cal, 2012, 9) 会在 cal 元素中产生如下的日历:

修改文档 - 图8

P.S. 对于此任务,简单生成日历就可以了,不需要提供点击功能。

八、为时钟上色

创建一个彩色时钟:

修改文档 - 图9

九、向列表中插入 HTML

写代码,在下面两个 <li> 之间插入内容 <li>2</li><li>3</li>

  1. <ul id="ul">
  2. <li id="one">1</li>
  3. <li id="two">4</li>
  4. </ul>

十、排序表格

这里有一张表:

修改文档 - 图10

可能还有更多行数据。

写代码,让数据能按照 "name" 列排序。

答案

一、

答案是:1 和 3

两个命令都是将 text 作为“普通文本”插入到 elem 中去的。

这有个例子:

  1. <div id="elem1"></div>
  2. <div id="elem2"></div>
  3. <div id="elem3"></div>
  4. <script>
  5. let text = '<b>文本内容</b>';
  6. elem1.append(document.createTextNode(text));
  7. elem2.textContent = text;
  8. elem3.innerHTML = text;
  9. </script>

二、

现在先来看一个错误的演示:

  1. function clear(elem) {
  2. for (let i=0; i < elem.childNodes.length; i++) {
  3. elem.childNodes[i].remove();
  4. }
  5. }

这种方法不能正常使用,因为每次调用 remove() 都会移动集合 elem.childNodes,保证集合中的元素始终是从索引 0 处开始的。但是 i 是递增的,因此有些元素会被忽略。

使用 for..of 循环的话也是一样。

正确的变体应该是:

  1. function clear(elem) {
  2. while (elem.firstChild) {
  3. elem.firstChild.remove();
  4. }
  5. }

当然还有一种更加简单的方式:

  1. function clear(elem) {
  2. elem.innerHTML = '';
  3. }

三、

所提供的 HTML 代码是不正确的,这就是发生奇怪事情的原因。

浏览器会自动修改我们代码的问题。在 <table> 中文本是不能直接作为孩子节点存在的:根据规范,只有特定于表格的标签被允许使用,因此浏览器会将 "aaa" 移动到 <table> 之前显示。

现在我们就能知道,当我们删除表格时,文本仍然存在的原因。

通过使用浏览器工具检查 DOM 可以轻松回答这个问题。检查结果显示 "aaa" 文本位于 <table> 之前。

HTML 规范详细说明了如何处理错误的 HTML,浏览器的这种行为是正确的。

四、

  1. let ul = document.createElement('ul');
  2. document.body.append(ul);
  3. var content = ''
  4. while (content = prompt('请输入内容')) {
  5. let li = document.createElement('li');
  6. li.textContent = data;
  7. ul.append(li);
  8. }

五、

  1. innerHTML 方法。
  1. function createTree (container, data) {
  2. container.innerHTML = walkData(data)
  3. }
  4. function walkData(data) {
  5. let keys = Object.keys(data)
  6. let lis = keys.reduce((lis, key) => {
  7. let childHtml = ''
  8. if (!isEmptyObject(data[key])) {
  9. childHtml = walkData(data[key])
  10. }
  11. lis += `<li>${key}${childHtml}</li>`
  12. return lis
  13. }, '')
  14. let html = lis ? `<ul>${lis}</ul>` : lis
  15. return html
  16. }
  17. function isEmptyObject(obj) {
  18. for (let prop in obj) {
  19. return false
  20. }
  21. return true
  22. }
  1. DOM 方法。
  1. function createTree (container, data) {
  2. container.appendChild(walkData(data))
  3. }
  4. function walkData(data) {
  5. let keys = Object.keys(data)
  6. return keys.reduce((ul, key) => {
  7. let childNode = null
  8. if (!isEmptyObject(data[key])) {
  9. childNode = walkData(data[key])
  10. }
  11. let li = document.createElement('li')
  12. li.textContent = key
  13. if (childNode) {
  14. li.appendChild(childNode)
  15. }
  16. ul.appendChild(li)
  17. return ul
  18. }, document.createElement('ul'))
  19. }
  20. function isEmptyObject(obj) {
  21. for (let prop in obj) {
  22. return false
  23. }
  24. return true
  25. }

六、

  1. let lis = document.getElementsByTagName('li');
  2. for (let li of lis) {
  3. // 统计当前 <li> 下的所有 <li> 标签个数
  4. let descendantsCount = li.getElementsByTagName('li').length;
  5. if (!descendantsCount) continue;
  6. // 直接将个数附加到文本中
  7. li.firstChild.data += ' [' + descendantsCount + ']';
  8. }

七、

  1. function createCalendar(cal, year, month) {
  2. var date = new Date(year, month - 1)
  3. // 1 号是周几
  4. var day = date.getDay()
  5. // 一共有几天
  6. date.setMonth(month, 0)
  7. var dates = date.getDate()
  8. // 日期占几行
  9. var offsetDict = [6, 0, 1, 2, 3, 4, 5]
  10. var offset = offsetDict[day]
  11. var rows = Math.ceil((offset + dates) / 7)
  12. // 先把日历框架遍历出来
  13. var frag = document.createDocumentFragment()
  14. for (let i = 0; i < rows; i++) {
  15. let tr = document.createElement('tr')
  16. tr.innerHTML = '<td>&nbsp;</td>'.repeat(7)
  17. frag.appendChild(tr)
  18. }
  19. // 然后填充日期
  20. var tds = frag.querySelectorAll('td')
  21. for (let i = 0; i < dates; i++) {
  22. let day = i + 1
  23. let td = tds[i + offset]
  24. td.textContent = day
  25. }
  26. var table = document.createElement('table')
  27. table.className = 'cal'
  28. thDict = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']
  29. table.innerHTML = `
  30. <thead>
  31. <tr>
  32. <th>${thDict[0]}</th>
  33. <th>${thDict[1]}</th>
  34. <th>${thDict[2]}</th>
  35. <th>${thDict[3]}</th>
  36. <th>${thDict[4]}</th>
  37. <th>${thDict[5]}</th>
  38. <th>${thDict[6]}</th>
  39. </tr>
  40. </thead>
  41. <tbody></tbody>
  42. `
  43. var tBody = table.tBodies[0]
  44. tBody.append(frag) // 将生成好的日期附加到 tBody
  45. // 将最终生成的 table 附加到 cal 中
  46. cal.append(table)
  47. }

八、

我们先来写 HTML/CSS:

每次时间组件(时、分、秒)都用 标签包装:

  1. <div id="clock">
  2. <span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
  3. </div>

同时,我们需要用一些 CSS 样式装饰一下。

update 函数负责更新时钟时间,内部使用 setInterval 间隔 1 秒实现:

  1. function update() {
  2. let clock = document.getElementById('clock');
  3. let date = new Date(); // (*)
  4. let hours = date.getHours();
  5. clock.children[0].innerHTML = add0(hours);
  6. let minutes = date.getMinutes();
  7. clock.children[1].innerHTML = add0(minutes);
  8. let seconds = date.getSeconds();
  9. clock.children[2].innerHTML = add0(seconds);
  10. }
  11. function add0(v) {
  12. return v.toString().padStart(2, '0')
  13. }

(*) 这个地方,每次都获取下当前时间。setInterval 调用并不可靠——可能会延迟发生。

管理时钟运行的函数:

  1. let timerId;
  2. function clockStart() { // 运行时钟
  3. update(); // (*)
  4. timerId = setInterval(update, 1000);
  5. }
  6. function clockStop() {
  7. clearInterval(timerId);
  8. timerId = null;
  9. }

请注意,对 update() 的调用不仅安排在 setInterval 中,而且在(*) 处还立即调用了。否则访问者必须等 1 秒后才能看到效果,在这之前都是空的。

九、

  1. one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>');

十、

解决方案很简短,但可能看起来有点棘手,所以在这里我提供了详细的注释:

  1. let sortedRows = Array.from(table.rows)
  2. .slice(1)
  3. .sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1);
  4. table.tBodies[0].append(...sortedRows);
  1. 获得所有的 <tr>,类似 table.querySelectorAll('tr'),然后转换成数组,因为我们要用到数组方法。

  2. 第一个 TR 实际上是表格表头,我们不需要它,只需要排序 .slice(1) 后剩下的。

  3. 我们排序比较的是第一个 <td> 里的内容(name 字段)。

  4. 现在通过 .append(...sortedRows) 将正确顺序的节点插入。
    表格总是包含一个隐藏元素 <tbody>,我们要插入到它里面,简单的使用 table.append(...) 会失败的。

需要注意的是:我们无需删除它们,只要“重新插入”就可以了,元素节点会自动离开老位置,转移到新位置的。

(完)