04元素增删
DOM 是活的——上一课我们只是「读」页面(找节点、读属性、看内容),
这一课开始「写」页面:createElement 凭空造元素、
append / insertBefore 插到任意位置、
remove / replaceWith 删掉或替换任何东西。
把这四件套收齐,JS 就真正能「画」出一整张网页了。
一、createElement 与文本节点
想改 DOM 之前,先得造一个节点。浏览器给了三件工具:
createElement 造元素、createTextNode 造纯文本、
createDocumentFragment 造一个「离线容器」。
造好的节点还不属于任何父元素,得手动插进去才会显示。
| 函数 | 造什么 | 返回 | 典型场景 |
|---|---|---|---|
| createElement('div') | HTML 元素节点 | HTMLDivElement |
造任意标签:li / p / button… |
| createTextNode('hi') | 纯文本节点 | Text |
不想要任何标签、只塞文字时 |
| createDocumentFragment() | 文档片段(虚拟父节点) | DocumentFragment |
批量插入前先「攒一起」,一次塞入 |
append / appendChild 等插入方法,
它才真正「上墙」。二、appendChild / append:插到末尾
parent.appendChild(child) 是最经典的「追加到末尾」API。
现代写法 parent.append(...nodes) 支持多参数、还能直接传字符串当文本。
注意:节点如果已经在 DOM 里,再 append 会被「搬家」,
不会复制——这常被用来做节点移动。
text 改成你自己的名字- 原有 1
- 原有 2
parent.append('hello') 会自动创建一个文本节点塞进去。
而 appendChild('hello') 会直接报错——它只收节点。三、insertBefore / before:精确插入
append 只会插到末尾。想插到指定元素前,得用
parent.insertBefore(newNode, refNode)——在 refNode 前面插入 newNode。
现代等价写法是 refNode.before(newNode),方向更直观:
「在这个节点前面加一个」。
newText 成你想插入的内容- 第 1 项
- 第 2 项(参考点)
- 第 3 项
parent.insertBefore(node, null)——第二个参数传 null,等价于追加末尾。
不过现代项目直接用 append 更直观。四、removeChild / remove:删除节点
节点长大也会被砍。parent.removeChild(child) 是经典写法:
需要知道父节点。现代写法 child.remove()
更省心——直接喊「我自己走」,不必回头问爸爸。
被 remove 的节点还在 JS 里,只是脱离了 DOM 树,还能再 append 回去。
| API | 调用者 | 参数 | 现代项目用 |
|---|---|---|---|
| parent.removeChild(child) | 父节点 | 要删的子节点 | 兼容老浏览器时 |
| child.remove() | 要删的节点自己 | 无 | 现代标配,省去「找爹」一步 |
| child.remove() 后再 append | — | — | 节点对象还在 JS 内存里,可塞回 DOM |
- 待删 A
- 待删 B
- 待删 C
- 待删 D
- 待删 E
append 回去就行——这是做「回收站」「撤销」功能的原理。五、replaceChild / replaceWith:替换节点
节点说换就换。经典写法 parent.replaceChild(newChild, oldChild):
「父节点喊一声,把老的换成新的」。现代等价是
oldChild.replaceWith(newChild)——「老的说自己让位」,
参数顺序和名字都对得上动作。
#old 替换成 <b>新文本</b>:① 用 replaceChild;② 用 replaceWith —— 看两种写法最终结果一样parent.insertBefore(oldEl, newEl) 把旧的临时挪开,
再 append 进去;或者直接用第三方库做 diff(React/Vue 干的活)。六、克隆节点:cloneNode
想复制一份现有节点?用 node.cloneNode(deep)。
传 true 深克隆(连子节点一起复制),
传 false 浅克隆(只复制外壳,里面是空的)。
克隆出来的节点仍是孤儿,必须手动 append 才会显示。
title 看新内容这是原卡片里的一段说明文字。
getElementById 只会拿到第一个——记得克隆后立刻改名。七、DocumentFragment:批量插入的性能秘密
往页面插 1000 个 <li>,直接循环 appendChild,
浏览器每插一个就重排一次(重新计算布局、重新绘制),
1000 次就是 1000 次重排——肉眼可见的卡顿。
正确做法:先把 1000 个 li 全部塞进一个DocumentFragment(文档片段),
再把这个片段一次性 appendChild 进去——浏览器只重排一次。
| 方案 | 重排次数 | 适用场景 |
|---|---|---|
| 循环 appendChild | N 次(每插一个一次) | 几行时无所谓 |
| Fragment + 一次 append | 1 次 | 列表渲染、批量新增节点 |
| cloneNode + Fragment | 1 次 + 0 重排 | 模板复用(如聊天记录批量追加) |
八、本章小结
- createElement / createTextNode / createDocumentFragment 三件套造节点,造出来都是「孤儿」,必须 append 才上墙。
- append / appendChild 追加到末尾;
append可多参数、可传字符串,appendChild只收节点。 - insertBefore / before 插到指定元素前;现代
refNode.before(newNode)方向更直观。 - removeChild / remove 删除节点;
child.remove()不用找爹,是现代标配。被删节点对象仍在 JS 里,可再 append 复活。 - replaceChild / replaceWith 替换节点;
oldChild.replaceWith(newChild)让旧节点自己让位。 - cloneNode(true) 深克隆(含子节点)、
cloneNode(false)浅克隆(只壳);克隆节点仍要 append 上墙,记得改 id。 - DocumentFragment 攒齐节点一次性 append,1 次重排代替 N 次——批量插入性能关键。
createElement + appendChild 拼一个迷你 todo list——
一个输入框 + 一个按钮,按钮点击时把输入框内容追加成新 <li>;
② 任务二:用 DocumentFragment 批量插入 100 行表格行,每行一个从 1 累加到 100 的编号,用 performance.now() 测耗时并打印到 __out 里;
③ 任务三:给 todo list 的每一项加一个「删除」按钮,点击时调用 li.remove() 删除自己;
④ 任务四(挑战):先用 cloneNode(true) 克隆一个模板卡片,把克隆体的 id 改名后插入页面——观察 id 不改时 getElementById 会出什么问题。