我有一个自定义元素提供API方法sayHello
。如果元素从DOM中删除,我需要“销毁”disconnectedCallback
中对自定义元素的所有引用。我怎样才能做到这一点?
class RemoveEl extends HTMLButtonElement {
constructor() {
super();
this.type= 'button';
this.addEventListener('click', () => {
this.parentElement.removeChild(this);
})
}
sayHello() {
console.log('hello');
}
disconnectedCallback() {
if (!document.body.contains(this)) {
console.log('removed');
// here I need something like
// this = null;
}
}
}
customElements.define('remove-el', RemoveEl, { extends: 'button' });
var sayHello = document.getElementById('sayHello');
var removeEl = document.getElementById('removeEl');
sayHello.addEventListener('click', () => {
if (removeEl) {
removeEl.sayHello();
}
})
<div>Test:
<button is="remove-el" id="removeEl">Click to remove</button>
<button id="sayHello" type="button">Say Hello</button>
</div>
根据我的理解,只要您在JavaScript端对元素进行引用,垃圾收集器将无法破坏您的元素,即使它已从DOM中删除。您的元素仍然有效,您可以在其上调用方法。
您必须自己管理参考。在自定义元素的disconnectedCallback
中,设置一个属性以将其标记为已删除,例如:this.destroyed = true
。
然后,您可以使用该属性来保护访问权限,但该元素不会被垃圾回收:
class RemoveEl extends HTMLButtonElement {
constructor() {
super();
this.type= 'button';
this.addEventListener('click', () => {
this.parentElement.removeChild(this);
})
}
sayHello() {
console.log('hello');
}
disconnectedCallback() {
if (!document.body.contains(this)) {
this.destroyed = true;
console.log('removed');
}
}
}
customElements.define('remove-el', RemoveEl, { extends: 'button' });
const sayHello = document.getElementById('sayHello');
const removeEl = document.getElementById('removeEl');
sayHello.addEventListener('click', () => {
if (removeEl && !removeEl.destroyed) {
removeEl.sayHello();
}
})
<div>Test:
<button is="remove-el" id="removeEl">Click to remove</button>
<button id="sayHello" type="button">Say Hello</button>
</div>
或者创建一个引用包装器,只有在内部引用有效的情况下才能应用函数,仍然垃圾收集将无法销毁引用,因为由于使用do
的el
函数现在有一个闭包:
class RemoveEl extends HTMLButtonElement {
constructor() {
super();
this.type= 'button';
this.addEventListener('click', () => {
this.parentElement.removeChild(this);
})
}
sayHello() {
console.log('hello');
}
disconnectedCallback() {
if (!document.body.contains(this)) {
this.destroyed = true;
console.log('removed');
}
}
}
customElements.define('remove-el', RemoveEl, { extends: 'button' });
const ref = el => ({ do: fn => { if (el && !el.destroyed) fn(el); } })
const sayHello = document.getElementById('sayHello');
const removeEl = ref(document.getElementById('removeEl'));
sayHello.addEventListener('click', () => {
removeEl.do(el => el.sayHello());
})
<div>Test:
<button is="remove-el" id="removeEl">Click to remove</button>
<button id="sayHello" type="button">Say Hello</button>
</div>
或者您可以使用代理来管理该引用。只要destroyed
为false,就会在对象上调用方法,但是一旦代理检测到destroyed = true
,它就会返回属性的默认值并销毁自己对元素的引用,这有望让垃圾收藏家摆脱它。
有点像这样:
class RemoveEl extends HTMLButtonElement {
constructor() {
super();
this.type= 'button';
this.addEventListener('click', () => {
this.parentElement.removeChild(this);
})
}
sayHello() {
console.log('hello');
}
disconnectedCallback() {
if (!document.body.contains(this)) {
this.destroyed = true;
console.log('removed');
}
}
}
customElements.define('remove-el', RemoveEl, { extends: 'button' });
const ref = (el, defaultEl) => {
let destroyed = el.destroyed;
const checkEl = () => {
if (!destroyed && el && el.destroyed) {
destroyed = true;
el = null;
}
return destroyed;
}
return new Proxy({}, {
get: (obj, prop) => {
return checkEl() ? defaultEl[prop] : el[prop];
}
});
}
const sayHello = document.getElementById('sayHello');
const removeEl = ref(document.getElementById('removeEl'), { sayHello: () => console.log('bye') });
sayHello.addEventListener('click', () => {
removeEl.sayHello();
})
<div>Test:
<button is="remove-el" id="removeEl">Click to remove</button>
<button id="sayHello" type="button">Say Hello</button>
</div>
有两个解决方案:
不要保留任何参考
自定义元素从DOM中删除后立即进行垃圾回收的方式。
//var removeEl = document.getElementById('removeEl');
sayHello.addEventListener('click', () => {
let removeEl = document.getElementById('removeEl')
if ( removeEl )
removeEl.sayHello();
})
管理全球参考
如果需要保留对自定义元素的全局引用,则需要将其设置为null以使对象被销毁。
您可以通过多种方式实现这一目标。例如,在元素断开连接时调度自定义事件并在参考级别处理它。
class RemoveEl extends HTMLButtonElement {
constructor() {
super();
this.addEventListener('click', () => this.parentElement.removeChild(this));
}
sayHello() {
console.log('hello');
}
disconnectedCallback() {
console.log('removed');
//dispatch a destroy event
var ev = new CustomEvent('destroyed');
document.dispatchEvent(ev);
}
}
customElements.define('remove-el', RemoveEl, { extends: 'button' });
var sayHello = document.getElementById('sayHello');
var removeEl = document.getElementById('removeEl');
//delete reference
document.addEventListener('destroyed', () => removeEl = null);
sayHello.addEventListener('click', () => removeEl && removeEl.sayHello())
<button is="remove-el" id="removeEl">Click to remove</button>
<button id="sayHello">Say Hello</button>