从 disconnectedCallback 中的 DOM 中删除自定义元素的所有引用为空
Null all references of custom element being removed from the DOM in disconnectedCallback
我有一个自定义元素提供 API 方法 sayHello
。如果元素从 DOM 中删除,我需要 "destroy" 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
为假,就会在对象上调用这些方法,但是一旦代理检测到 destroyed = true
,它就会 return 属性的默认值并销毁它自己对该元素的引用,这有望让垃圾收集器摆脱它。
有点像这样:
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>
有 2 个解决方案:
不要保留任何引用
自定义元素从 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>
我有一个自定义元素提供 API 方法 sayHello
。如果元素从 DOM 中删除,我需要 "destroy" 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
为假,就会在对象上调用这些方法,但是一旦代理检测到 destroyed = true
,它就会 return 属性的默认值并销毁它自己对该元素的引用,这有望让垃圾收集器摆脱它。
有点像这样:
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>
有 2 个解决方案:
不要保留任何引用
自定义元素从 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>