先记住这个答案
removeChild需要先取得父节点,再传入要删除的子节点引用,成功时返回被删除节点;失败比如传入非子节点会抛出NotFoundError。remove则是节点自己调用,会从父节点中移除自身,调用后返回undefined,即使没有父节点也不报错,且作用于Element等ChildNode实现者。
- removeChild需父节点调用并传子节点
- remove是子节点自己删除自己
- removeChild失败抛错,remove静默忽略
底层操作路径与调用约定
removeChild(child)是Node接口方法,由父节点调用并传入子节点,成功后返回该子节点。若child不是当前节点的直接子节点,会抛出NotFoundError;移除后child的parentNode变为null,但child对象本身仍可继续使用,只是不在DOM树中。
Element.remove()来自ChildNode混合接口,调用方是自己,内部逻辑近似于取出自己的parentNode并执行removeChild(this)。若没有父节点则直接返回,不报错。remove的返回值恒为undefined,因此不能像removeChild那样拿回被删节点;从2015年起广泛支持,现代浏览器可放心使用。
任务列表中删除当前项的两种写法
假设一个待办列表,每个li都有删除按钮,点击后需要移除该li。用removeChild需要先找到按钮所在li,再调用li.parentNode.removeChild(li);用remove则直接li.remove()。在按钮事件回调中,后者更简短且无需判断父节点是否存在。
但如果删除逻辑放在管理列表中某模块,外部持有container和将要删除的child,此时用container.removeChild(child)更直观,且能拿到返回节点进行后续数据结构清理;若担心child已被其他逻辑移除,先判断child.parentNode === container再调用removeChild,能避免抛错。
适用边界与失效条件
removeChild要求调用者与子节点存在真实父子关系。如果节点位于Shadow Root或其他文档树中,也必须通过该树内的父节点调用。若子节点刚被移动到其他容器,旧父节点的removeChild调用必定抛错,可通过child.parentNode是否等于当前容器来提前规避。
remove的限制是调用者必须实现ChildNode接口,Element、CharacterData、DocumentType都支持,但Document和DocumentFragment不支持。业务代码中若不关心父节点和返回值,推荐remove;若需要确认删除结果,先用parentNode比较再做removeChild,代价仅是多次属性读取。
容易答错的地方
- removeChild 和 remove 在任何情况下都可用
- removeChild要求传入的节点必须是调用者的直接子节点,否则抛NotFoundError;remove则要求节点类型实现ChildNode接口,Document节点不能调用remove。二者各有前提,不是无条件替换。
- remove() 会返回被移除的节点
- remove方法返回undefined,只有removeChild会返回被删除的节点。如果后续需要引用该节点做缓存或转移,应选用removeChild或先从变量保存引用。
面试官还会怎么问?
remove() 移除后元素还会占用内存吗?
只要还有JavaScript引用,元素仍驻留内存;若所有引用被清空,GC会回收它。与DOM树脱离不影响对象存续,只是不可见不可交互。
想同时移除多个子节点用哪个更好?
两者都一次移除一个节点,多个节点需循环。循环时注意HTMLCollection等实时集合会动态更新,建议倒序遍历或转为数组。若只需清空内容,可用textContent或innerHTML,但这不会自动清除节点上的事件监听器,仍持有引用的旧节点对象会保留监听器;彻底释放需清空引用。
如果子节点不是直接子节点,removeChild抛什么错误?
抛DOMException,name为NotFoundError,因为removeChild只能移除直接子节点,不能删除孙子节点。remove对于非直接子节点调用自己的方法不会检查,只移除自己,不涉及此错误。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。