Learning Objectives
- Remove elements with
remove()and read the olderremoveChild()pattern in existing code - Swap nodes with
replaceWith()and empty containers efficiently - Explain why deletion loops over live collections run bottom-up
- Clean up event listeners — by delegation, and by
AbortController
Taking a node out
The modern call is the obvious one: the element removes itself.
The original DOM had no such method. Removal was a parental act: you found the parent and asked it to disown the child, and the call handed the removed node back in case you wanted to keep it.
You will read removeChild in nearly every codebase older than a few years, so know it — but write remove() and its sibling replaceWith(), which put the verb on the node you are actually acting on. A removed node is not destroyed, by the way: if you kept a reference, it is merely detached, and you can append it somewhere else. Moving a node is removing and re-inserting it.
Emptying a container
To clear a list wholesale, the blunt instrument is fine, and it is what the demo's Clear button does:
If you loop instead — say, removing only some children — remember what step 2 taught about live collections: children re-indexes itself as you delete, so a forward loop skips every other element. The classic fix is to run the loop from the end, where deletions cannot shift what you have not visited yet:
querySelectorAll list, or go bottom-up. This bug is a rite of passage; passage is faster if you know it is coming.Live demo
Add items, remove them one at a time, clear the lot. Note that the per-item Remove buttons carry no listeners of their own — one delegated listener on the container (step 4 of the Events series) handles every button that will ever exist in it:
Destruction is more than nodes
Removing an element does not remove what points at it. A listener registered on window that closes over your element, an interval that touches it, a Map that keys on it — any of these keeps the “removed” node alive and the work firing. This is the leak everyone writes at least once. Two habits prevent it. Delegation is the first: listeners live on the container, so removing children removes nothing that needs cleanup. The second is AbortController, which the demo's second section uses — pass a signal when you listen, and one abort() detaches everything registered with it:
addEventListener it makes, turns cleanup from a checklist into a single call — the same pattern that cancels a fetch.Next Steps
You can now select, walk, read, write, create, and destroy — the full manipulation vocabulary. In Step 8: Geometry and observers, we ask the tree where things actually are:
- Measure with
getBoundingClientRect()and theoffset/client/scrollfamilies - Understand what reading layout costs
- Let
IntersectionObserveranswer “is it visible?” without polling