Consider an implementation for a LinkedList or a Tree. You might not want to expose next, last, children, or parent as a property, but instead make it a property accessor that checks a static WeakMap for the existence of such relationships. This way, your implementation allows the relationships to maintain weak connections.
HTML elements are a good way of explaining this. Let's say you partially implement an HTML element:
const HTMLElement = (() => {
const children = new WeakMap()
class Node {
constructor () {
children.set(this, [])
}
get children () {
return children.get(this)
}
appendChild (element) {
children.get(this).push(element)
return element
}
removeChild (element) {
const elements = children.get(this)
elements.splice(elements.indexOf(element), 1)
return element
}
}
return class HTMLElement extends Node {
constructor (tag) {
super()
this.tagName = tag.toUpperCase()
}
toString () {
return `<${this.tagName}>${children.get(this).join('')}</${this.tagName}>`
}
}
})()
{
const p = new HTMLElement('p')
p.appendChild(new HTMLElement('a'))
p.appendChild(new HTMLElement('span'))
console.log(`${p}`)
}
// a and span get GC'd when reference to p is lost
console.log(typeof p)
If children was a Map, you'd have a memory leak when the reference to p is lost, because the others would still have strong references since HTMLElement is still accessible.