【问题标题】:Null all references of custom element being removed from the DOM in disconnectedCallback在 disconnectedCallback 中从 DOM 中删除自定义元素的所有引用
【发布时间】:2019-07-22 06:13:07
【问题描述】:

我有一个自定义元素提供 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 ecmascript-6 custom-element


    【解决方案1】:

    据我了解,只要您在 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,它就会返回属性的默认值并销毁它自己对元素的引用,希望会让垃圾收集器摆脱它。

    有点像这样:

    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>

    【讨论】:

    • 实际上即使在第三种解决方案中,按钮也不会因为在 ref 函数签名 (el) 中添加的引用而被破坏
    • 将引用设置为 null 会将其从函数范围中释放出来
    • 是的,但这只会在从 DOM 中移除后调用其中一个方法后才会发生(这可能会在以后发生或永远不会发生),而不是在 disconnectedCallback 之后立即发生。
    【解决方案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>

    【讨论】:

      猜你喜欢
      • 2022-11-21
      • 2010-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-06
      • 2011-04-26
      • 2014-09-01
      • 1970-01-01
      相关资源
      最近更新 更多