Обнулить все ссылки на пользовательский элемент, удаляемый из DOM в disconnectedCallback - PullRequest
0 голосов
/ 28 февраля 2019

У меня есть пользовательский элемент, предлагающий метод API sayHello.Мне нужно «уничтожить» все ссылки на пользовательский элемент в disconnectedCallback, если элемент удаляется из DOM.Как мне этого добиться?

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>
  

Ответы [ 2 ]

0 голосов
/ 02 марта 2019

Существует 2 решения:

Не сохраняйте никаких ссылок

Способ сбора пользовательского элемента после его удаления из DOM.

//var removeEl = document.getElementById('removeEl');

sayHello.addEventListener('click', () => {
    let removeEl = document.getElementById('removeEl')
    if ( removeEl )
        removeEl.sayHello();   
})

Управление глобальными ссылками

Если вам нужно сохранить глобальную ссылку на свой пользовательский элемент, вам нужно установить его в null, чтобы получить объектуничтожен.

Вы можете достичь этого многими способами.Например, Dispach пользовательского события, когда disconneced элемента и обработать его на уровне эталонного.

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>
0 голосов
/ 28 февраля 2019

Насколько я понимаю, пока вы держите ссылку на свой элемент на стороне 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>
...