事件、事件代理、事件冒泡

00x0 写在前面

  这是一篇总结博客。

00x1 事件与事件流

  1. 事件:HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。
  2. 事件流:事件流描述的是从页面中接受事件的顺序。事件发生时会在元素节点与根节点之间按照特定的顺序传播,路径所经过的所有节点都会收到该事件,这个传播过程即DOM事件流。事件传播的顺序对应浏览器的两种事件流模型:捕获型事件流和冒泡型事件流。
    • 冒泡型事件流:事件的传播是从最特定的事件目标到最不特定的事件目标。即从DOM树的叶子到根。
    • 捕获型事件流:事件的传播是从最不特定的事件目标到最特定的事件目标。即从DOM树的根到叶子。
      image

  “DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,然后处于目标阶段,最后才事件冒泡。

00x2 事件冒泡

  IE在处理事件流的时候,事件的传播是按照从最特定的事件目标到最不特定的事件目标(document对象,有的浏览器是window)的顺序触发。这就是事件冒泡。

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<div id="content">
<button id="btn"></button>
</div>
</body>
</html>

  在上面的代码中,如果点击了按钮“< button >”节点,那么接下来,“< div >”、“< body >”、“< html >”、document会按照先后顺序依次收到点击事件。
  事件冒泡仅仅传递的是事件触发,也就是说,当点击按钮button的时候仅仅触发了div、body、html、document的点击事件,并没有把自己绑定的函数传递给父级。父级的执行情况,还是取决于自己所绑定的函数。在父级没有绑定函数的情况下,并没有什么影响。但是在父级绑定了函数的情况下,冒泡就会带来一些困扰。这个时候就需要取消事件冒泡。

  • event.stopPropagation():阻止事件的冒泡,不让事件向documen上蔓延,但是默认事件仍然会执行,当使用这个方法的时候,如果点击一个连接,这个连接仍然会被打开。该方法只阻止一次事件冒泡并且IE不支持此方法。IE取消事件冒泡要用e.cancelBubble=true。
  • event.preventDefault()方法:阻止默认事件,调用此方法时,链接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素。
  • return false :这个方法比较暴力,会同时阻止事件冒泡和默认事件;写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;可以理解为return false就等于同时调用了event.stopPropagation()和event.preventDefault()。

00x3 事件代理

  事件代理还可以叫事件委托,指的是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
  比如在一个页面中,有100个button,每个button都有一个点击事件。那要怎么做呢,首先想到的是,我们可以给每个button onclick绑定点击事件,那这样就需要给每个button写一个onclick来进行绑定,增加了代码的重复度。再高级一点的方法呢,我们可以用dom来处理事件,使用for循环的方法,遍历所有的button,使用dom给他们添加点击事件。
  但是这个方式也不是最完美的,因为在JavaScript中,添加到页面上的事件处理程序数量会直接关系到页面的整体运行性能。for循环中需要不断地与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也越多,会延长整个页面的交互时间。性能优化的主要思想之一就是减少DOM操作。这个时候就可以利用事件代理,将所有操作放入js程序中,与DOM只交互一次,大大减少了与DOM的交互次数,提高了性能。

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<div id="content">
<button id="btn"></button>
</div>
</body>
</html>

  事件代理是利用事件冒泡实现的。上面我们已经讲过,事件冒泡就是事件从最深的节点开始,然后逐步向上传播事件。当我们给button添加一个点击事件,这个事件会一层一层往外执行,div、body、html、document会一次收到这个事件。事件代理就是采用父级的div来做事件处理,由于冒泡原理,点击button事件会被冒泡到button的父级元素div上,如果div上添加的有点击事件,这时点击事件就会被触发;当然,点击div的时候,事件也是会触发的。那如果我们想让事件代理的效果跟直接给节点绑定的效果一样,需要怎么做呢?
  Event对象提供了一个target属性,可以返回事件的目标节点,成为事件源。我们可以将target理解为当前事件要操作的DOM,当然这并不是在真正地操作DOM。标准浏览器使用ev.target,IE浏览器用event.srcElement。此时只是获取了当前节点的位置,要想知道节点的名称,需要用nodeName来获取节点具体的标签名。需要注意的是,nodeName返回的值是大写的,习惯上把这个值转成小写的再使用。

1
2
3
4
5
6
7
8
9
10
11
window.onload = function(){
  var div = document.getElementById("content");
  div.onclick = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if(target.nodeName.toLowerCase() == 'button'){
         alert(123);
        alert(target.innerHTML);
    }
  }
}

  经过这样修改后就只有在点击button的时候才会触发事件了,且每次只用执行一次DOM操作。

Miss Me wechat
light