节点移除和事件监听函数触发问题

1,388 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

问题:假设有父子关系的两个div,子级div触发点击事件,然后删除父级元素节点,父级元素上的点击事件会被触发吗?

问题详情:

父级div的id为parent,子级div的id为son,父级div的点击事件监听函数为handleEmit,子级div的点击事件监听函数为handleRemove,handleRemove将移除id为parent的元素,而handleEmit会在控制台输出'emit'。请问当点击子级div时,handleEmit方法会打印'emit'吗?

可能的误区:

子级触发方法移除父级元素节点之后,父级元素节点已经从页面上消失,所以不会在触发父级上点击事件监听函数的方法

实际发生的事情:

子级触发点击事件方法之后,该方法移除父级节点,但是同时子级节点仍然在该父级容器中,所以事件冒泡依然会到父级节点上。

以下是示例代码:

 <div id="removedNode">removed
	<div id="emitNode">emit</div>
 </div>
 <script>
    document.querySelector('#emitNode').addEventListener('click', emitRemove)
    document.querySelector('#removedNode').addEventListener('click', handleRemoveEvent)
    function emitRemove(){
	console.log('emitRemove')
	const node = document.querySelector('#removedNode')
	document.querySelector('body').removeChild(node)
    }
    function handleRemoveEvent(){
	console.log('handle')
    }
</script>

输出结果将是:

emitRemove
handle

而如果你希望将节点移除之后,父级元素parent上的点击事件监听函数不再触发,那么你要在emitRemove方法体内将parent上的事件监听函数移除掉,也就是说:

  function emitRemove(){
      console.log('emitRemove')
      const node = document.querySelector('#removedNode')
      node.removeEventListener('click',handleRemoveEvent) //新增
      document.querySelector('body').removeChild(node)
  }

新增移除事件监听函数的代码,并执行这个函数之后,父级元素上的点击事件监听函数将不会再触发。 现在的输出结果:

 emitRemove

总结,以及最后的tip

尽管很难去想象到这个事,但是你要知道移除了父节点之后,只是DOM文档上没有了该节点,但是依然会执行父节点上的方法。接下来还有另一个demo可以让我们观察到这个现象。 示例代码如下:

    <div id="removedNode">removed
	<div id="emitNode">emit</div>
    </div>
    <script>
	let emitNode = document.querySelector('#emitNode')
	let removedNode = document.querySelector('#removedNode')
	emitNode.addEventListener('click', emitRemove)
	removedNode.addEventListener('click', handleRemoveEvent)
	function emitRemove(){
            console.log('emitRemove')
            // removedNode.removeEventListener('click',handleRemoveEvent)
            if(!document.querySelector('body').contains(removedNode)){
                return
            }
            document.querySelector('body').removeChild(removedNode)
	}
	function handleRemoveEvent(){
            console.log('handle')
	}
    </script>

当你在控制台点击emit时,会触发emitRemove方法移除父节点,并且打印后续的handle。但是如果你在控制台继续执行emitNode.click(),尽管此时页面上已经没有了这两个div,但是id为removedNode的节点上的事件监听函数依然会被触发。也就是说,控制台会打印:

   emitRemove
   handle