事件委托(事件代理)

1,045 阅读4分钟

  • 概念:

通俗的说是这个事件需要绑定在我身上的,绑定在别人身上,也能达到同样的效果,也就是绑定在别人身上,我也能监听到。

  • 产生的原因:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<ul id="ul-test">
    <li>0</li>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
    <li>9</li>
</ul>
</body>
<script type="text/javascript">
  var oUl=document.getElementById("ul-test");
  var oLi=oUl.getElementsByTagName("li");
  for(var i=0,len=oLi.length;i<len;i++){
    oLi[i].addEventListener("click",function(){
      alert(this.innerHTML)
    })
  }
</script>
</html>

问题在于:
1.for循环,循环的是li,10个li就循环10次,绑定10次事件,1000个就循环了1000次,绑定1000次事件!
2.如果li不是本来就在页面上的,是未来元素,是页面加载了,再通过js动态加载进来了,上面的写法是无效的,点击li是没有反应的!

应该怎么解决以上问题?

  • 解决的办法就是通过事件委托:

var oUl=document.getElementById("ul-test");
oUl.addEventListener("click",function(ev){
  console.log(ev)
  var ev=ev||window.event;
  var target=ev.target||ev.srcElement;
  //如果点击的最底层是li元素
  if(target.tagName.toLowerCase()==='li'){
    alert(target.innerHTML)
  }
})

  •   原理:

  1. 事件的传播有三个阶段:

当一个事件发生以后,它会在不同的DOM节点之间传播(propagation)。这种传播分成三个阶段:

  • 第一阶段:从window对象传导到目标节点,称为“捕获阶段”(capture phase)。

  • 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。

  • 第三阶段:从目标节点传导回window对象,称为“冒泡阶段”(bubbling phase)。

这种三阶段的传播模型,会使得一个事件在多个节点上触发。比如,假设点击<div>之中嵌套一个<p>节点。

<div>
  <p>Click Me</p>
</div>

如果对这两个节点的click事件都设定监听函数,则click事件会被触发四次。

var phases = {
  1: 'capture',
  2: 'target',
  3: 'bubble'
};

var div = document.querySelector('div');
var p = document.querySelector('p');

div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);

function callback(event) {
  var tag = event.currentTarget.tagName;
  var phase = phases[event.eventPhase];
  console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}

// 点击以后的结果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'
  1. 捕获阶段:事件从<div><p>传播时,触发<div>click事件;
  2. 目标阶段:事件从<div>到达<p>时,触发<p>click事件;
  3. 目标阶段:事件离开<p>时,触发<p>click事件;
  4. 冒泡阶段:事件从<p>传回<div>时,再次触发<div>click事件。

注意,用户点击网页的时候,浏览器总是假定click事件的目标节点,就是点击位置的嵌套最深的那个节点(嵌套在<div>节点的<p>节点)。所以,<p>节点的捕获阶段和冒泡阶段,都会显示为target阶段。

事件传播的最上层对象是window,接着依次是documenthtmldocument.documentElement)和bodydocument.body)。也就是说,如果<body>元素中有一个<div>元素,点击该元素。事件的传播顺序,在捕获阶段依次为windowdocumenthtmlbodydiv,在冒泡阶段依次为divbodyhtmldocumentwindow

定义:

由于事件会在冒泡阶段传向父节点,因此可以把子节点的监听函数绑定在父节点上,用父节点的监听函数统一处理多个子节点的事件,这就是事件代理。

var ul = document.querySelector('ul');

ul.addEventListener('click', function(event) {
  if (event.target.tagName.toLowerCase() === 'li') {
    // some code
  }
});

上面这个函数事件监听绑在ul身上,但是监听到点击li的事件,然后处理li点击后的回调。

就算li是动态添加进来的依然能够监听到。

总结:

由于一个事件在传播的过程中有三个阶段,由最外层的父节点一层层传递到最里面的子节点,先是捕获阶段,到目标阶段,再到冒泡阶段。如果我们想监听到动态添加的子节点的事件,我们可以把事件绑在父节点上,利用event.target,筛选到子节点,处理事件相应的回调!!!