先记住这个答案
createElement 按标签名创建全新元素,初始无属性、无子节点。cloneNode 复制已有节点,可选深拷贝子树,并保留属性及属性内联事件,但不会复制通过 addEventListener 或 onclick 属性赋值添加的监听器。二者均生成无父节点的节点,需 appendChild 等插入。
- createElement生成空白元素
- cloneNode保留属性但丢监听
- 深拷贝才复制子节点
底层机制差异
createElement 在调用文档的上下文中创建新元素,其 ownerDocument 为当前文档,元素初始没有 id、class 等任何属性,也没有子节点。浏览器会分配一个全新的节点对象,内部状态接近空壳。
cloneNode 则遍历原节点的属性并复制到新节点,对于属性节点保留其值。若 deep 为 true,则递归复制所有后代节点;为 false 或省略则只复制节点本身。重要的是,通过 addEventListener 添加的监听器以及通过属性赋值(如 node.onclick = fn)设置的事件处理程序不会被复制,因为它们存储在节点的内部数据结构中。而 HTML 属性中的内联事件(如 onclick="fn()")作为属性会被复制,但不会在复制期间触发。
动态表格行复制场景
假设一个订单表格,每行包含输入框、删除按钮,并已通过 addEventListener 绑定事件。用户点击“添加相同项”时,需要复制当前行。若用 cloneNode(true) 快速复制,得到的克隆行包含输入值和按钮,但按钮的点击监听并未复制,导致操作失效。
处理方式:先克隆,再重置输入值,然后遍历按钮重新绑定监听。或者,改用 createElement 逐行构造,每次显式设置属性和事件。权衡下,克隆减少 DOM 结构创建开销,但必须手动恢复事件;新建则代码冗余但逻辑清晰。本例选用克隆并手动重绑,代码量稍多但保留原始样式结构。
易失效的边界条件
cloneNode 不会复制 <canvas> 的绘制图像,克隆后画布内容为空。此外,deep 参数对空元素如 <img>、<input> 无效,它们本身不能有子节点。
用于 <template> 内容时需格外小心:template.content 属于另一文档,直接 cloneNode(true) 不会触发自定义元素升级。此时应使用 document.importNode(),它可以按照目标文档的注册表构建自定义元素,而 cloneNode 无法做到。
容易答错的地方
- 误以为克隆会复制事件
- 常见错误是认为
cloneNode拷贝所有东西。实际只有 HTML 属性中的内联事件(如onclick="fn()")会被复制,而addEventListener和element.onclick = fn均丢失,需手动重新绑定。 - 忽略 ID 重复问题
- 克隆带
id的元素并插入同一文档会导致重复 ID,违反 HTML 唯一性约束。应主动修改克隆件的id。使用name属性时也需考虑是否需要重命名。
面试官还会怎么问?
`importNode` 和 `cloneNode` 有何不同?
importNode 从源文档复制节点到目标文档,并按照目标文档的自定义元素注册表构建元素;cloneNode 始终在源文档的上下文中复制。跨文档克隆应选用 importNode。
如何复制一个元素但不包含子节点?
调用 element.cloneNode(false) 或省略参数,得到仅含元素自身属性、无后代的副本。若有事件监听,仍需手动重新绑定。
克隆的 `<canvas>` 为什么是空白的?
cloneNode 不会复制渲染上下文中的像素数据,只复制元素属性(如 width、height)但不会复制绘制的图像内容。画布内容需通过重新绘制或手动复制像素来保留。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。