【浏览器】浅谈事件冒泡和事件捕获

2,589 阅读4分钟

发现在写事件的时候,有很多零散的知识点傻傻分不清除,由此记录一下学习过程

事件(Event)

事件(Event)是由DOM元素产生的资源,它可以由 JavaScript 代码操作,这样说很抽象,其实可以举一些常见的事件例子:

  • 当用户单击鼠标时
  • 网页加载后
  • 加载图像后
  • 当鼠标移到元素上时
  • 更改输入字段时
  • 提交 HTML 表单时
  • 当用户按下某个键时

事件流(Event flow)

事件流描述的是页面接受事件的顺序

事件会在元素节点之间按照特定的顺序进行传播,这个传播过程就叫做DOM事件流

事件流的过程

考虑这样一个情况,三个div嵌套,且三个div都注册了鼠标点击事件,如果点击红色区域的部分,会按照什么顺序执行呢?

  • 蓝->绿->红(从外层到内层,Netscape Navigator提出,也就是事件捕获)
  • 红->绿->蓝(从内层到外层,IE提出,也就是事件冒泡)
  • 蓝->绿->红->绿->蓝 (先捕获,到达事件目标红色元素开始冒泡,W3C提出的中立说法)

1677552209348.png

   <style>
        #grandpa{
            width: 400px;
            height: 400px;
            background-color: blue;
        }
        #father{
            width: 300px;
            height: 300px;
            background-color: green;
        }
        #son{
            width: 200px;
            height: 200px;
            background-color: red;
        }
    </style>
<body>
    <div id="grandpa">                                
        <div id="father">                              
              <div id="son">
                在红色区域到底是谁先被触发点击事件?
                </div>      
        </div>
    </div>
</body>

按照W3C的说法,dom事件流分为3个步骤

  1. 捕获阶段 document先接收到点击(监听)事件 但没有绑定事件,就会略过,再到html 同样没有绑定略过,再到body同理,但是蓝色和绿色的div有绑定点击事件会触发
  2. 当前目标阶段 找到了红色div,以及它的绑定点击事件
  3. 冒泡阶段 事件由最具体的元素接收到了以后又逐层往上传播到dom最顶层的过程
image.png

而我们可以通过element1.addEventListener(type, listener, useCapture)来指定事件处理程序在哪个阶段执行

如果其最后一个参数为 true,则为捕获阶段设置事件处理程序,如果为 false,则为冒泡阶段设置事件处理程序。

但实际中我们更多关注的是冒泡阶段,所以用 element1.onclick = doSomething2可以直接设定为冒泡阶段

注意:

  1. js代码中只能执行捕获或者冒泡其中的一个阶段
  2. onclick和attachEvent只能得到冒泡阶段
  3. addEventListener(type,function,true/false)
  4. 实际使用中更关注的是事件冒泡
  5. 有些事件是没有冒泡的 onblur onfocus onmousenter onmouseleave

官方是推荐使用addEventListener()来注册一个事件监听器的,理由如下(引自MND原文):

  • 它允许为一个事件添加多个监听器。特别是对库、JavaScript 模块和其他需要兼容第三方库/插件的代码来说,这一功能很有用。
  • 相比于 onXYZ 属性绑定来说,它提供了一种更精细的手段来控制 listener 的触发阶段。(即可以选择捕获或者冒泡)。
  • 它对任何事件都有效,而不仅仅是 HTML 或 SVG 元素。

事件对象

var div = document.querySelector("div");
div.onclick = function(event) {}

1 event也就是一个事件对象,写到我们侦听函数的小括号里面,当成形参来看

2 事件对象只有有了事件才会存在,是系统自动给创建的,不需要我们传递参数

3 事件对象 就是一个对象,里面有我们事件一系列相关数据的集合 跟事件相关的 比如按下鼠标的坐标 按下键盘的keyCode

4 这个事件对象可以自己命名,比如event, e ,evt等

5 事件对象有兼容性问题,ie 678通过window.event来识别 兼容性处理方式 e = e || window.event

事件对象的常见属性和方法

e.target

返回触发事件的对象

e.target返回的是触发事件的对象(元素)谁触发了这个事件

Event.currentTarget 总是指向事件绑定的元素

Event.target 则是事件触发的元素

比如触发事件是绑定在ul上的,我点击里面的li的时候,Event.currentTarget始终指向的是ul

但是因为点击的是li,所以Event.target 会指向li。

 function hide(e){
  e.currentTarget.style.visibility = "hidden";
  console.log(e.currentTarget);
  // 该函数用作事件处理器时:this === e.currentTarget
}

var ps = document.getElementsByTagName('p');

for(var i = 0; i < ps.length; i++){
  // console: 打印被点击的 p 元素
  ps[i].addEventListener('click', hide, false);
}
// console: 打印 body 元素
document.body.addEventListener('click', hide, false);

e.type

返回触发事件的类型 hover click

e.cancelBubble

取消冒泡属性

e.preventDefault()

阻止默认的跳转链接

e.stopPropagation()

阻止冒泡

事件委托(委派)

不是按班里的人发东西,而是把东西交给班长,让班长去发

事件委托的原理:

不是每个子节点单独设置事件监听器,而是事件监听器设置在父节点上,然后利用冒泡原理影响设置每个子结点

给ul注册点击事件,利用事件对象target来找到当前点击的li,因为点击li,事件会冒泡到ul上,ul有注册事件,就会触发事件监听器,这样相当于ul里面的所有li都绑定了事件处理程序

事件委托的作用:

只操作了一次DOM,提高了程序的性能