Home > Mobile >  Why addEventListener works unexpected on element remove?
Why addEventListener works unexpected on element remove?

Time:07-30

$lis = document.getElementsByTagName('li');

for (let i = 0; i < $lis.length; i  ) {
  $li = $lis[i];
  $span = $li.getElementsByTagName('span')[0];
  $button = $li.getElementsByTagName('button')[0];

  $button.addEventListener('click', $li.remove);
}
<ul>
  <li>
    <span>1</span>
    <button>x</button>
  </li>
</ul>

On the last line I'm using Element.remove() method, but instead of deleting the <li> it deletes <button>.

Explain to me, please, why. I feel that there is something with this happening, but I want to understand this completely not just feel.

P.S. I also understand that I can solve this by wrapping the $li.remove in an anonymous function, but the point of my question is to understand described behaviour not just to solve the problem. Thanks.

CodePudding user response:

It's because the button because the new context or this that Element.remove() is called with. An anyonmous function, or more accurately, an arrow function, uses the lexical scope that it's declared in as the context, that's why it works.

So button.addEventListener('click', elementX.remove) actually takes the prototype function remove and calls it with the button as the context or in other words, it binds it to the button.

A way to demonstrate binding can work, is by binding remove to the element, then adding it as the event handler $li.remove.bind($li) this way, regardless of the implicit context, we've explicitly set it to $li.

$lis = document.getElementsByTagName('li');

for (let i = 0; i < $lis.length; i  ) {
    $li = $lis[i];
    $span = $li.getElementsByTagName('span')[0];
    $button = $li.getElementsByTagName('button')[0];
    let remove = $li.remove.bind($li)

    $button.addEventListener('click', remove);
}
<ul>
  <li>
    <span>1</span>
    <button>x</button>
  </li>
</ul>

CodePudding user response:

In this line $button.addEventListener('click', $li.remove); you are passing the $li's remove method as your click handler. The nature of click handlers (and other user-event handlers) is to rebind this in the handler function to the element you attach the listener to, so what really happens is

$li.remove.call($button)

You can fix it using bind:

$button.addEventListener('click', $li.remove.bind($li))

Sidenote:

Replace the live HTMLCollection document.getElementsByTagName('li') with a static NodeList by using document.querySelectorAll('li').

Removing an element from the DOM will also immediately remove it from any HTMLCollection it is part of, messing up your indexes.

  • Related