js基础之事件模型:事件冒泡事件捕获

1,034 阅读5分钟

概要:本文主要记录js事件模型相关的知识,主要讲解事件冒泡事件捕获,以及阻止冒泡,事件相关的方法。还有因为事件冒泡引发的鼠标事件之间的区别

js事件模型发展历史

原始事件模型(DOM0级的事件模型)

兼容所有浏览器,不存在事件流概念。事件发生立马处理。特点就是:没有办法为同一个节点绑定多个事件,后绑定的事件会覆盖前面的事件。事件发生的阶段依然是在冒泡的阶段发生的。

  • 实现方式1:直接在dom元素上绑定事件 如:onclick=""
  • 实现方式2: 通过js的方式绑定事件 dom.onclick=function

标准事件模型(dom2级事件模型)

标准事件模型是W3C组织制定的标准事件模型,现代浏览器(IE6~8之外)都支持,该模型将事件分为三个阶段: 在标准事件模型中(DOM2级事件中),当事件发生在节点时,目标元素的事件处理函数就被触发,而且目标的每个祖先节点也有机会处理那个事件。

  • 捕获阶段:当某个事件触发时,事件会从window对象,自上而下传播,直至事件发生的目标元素,默认在这个过程中相应的事件监听函数不会触发。

  • 目标阶段:当事件传播到目标元素之后,执行目标元素上,该事件的监听函数,如果没有就不执行。

  • 冒泡阶段:事件再从目标元素开始逐层向上传播, 如果途中,有该事件的监听函数就执行。 所有事件都有捕获阶段,但是只有部分事件才有冒泡阶段。具体详情见: www.w3.org/TR/DOM-Leve…

标准事件模型对应的事件绑定方法

 element.addEventListener(event, function, useCapture)
 
 useCapture:
 - true - 事件句柄在捕获阶段执行(即在事件捕获阶段调用处理函数)
 - false- 默认值。事件句柄在冒泡阶段执行(即表示在事件冒泡的阶段调用事件处理函数)

阻止事件冒泡:

  1. 使用方法event.stopPropagation()
$("#div1").mousedown(function(e){
    var e=event||window.event;
    event.stopPropagation();
});
  1. 加上条件判断event.target==event.currentTarget,让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡;
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="btn1">
    btn1
    <div id="btn2">
      btn2
      <div id="btn3">btn3</div>
    </div>
  </div>
</body>

<script>
  var btn1 = document.getElementById("btn1");
  var btn2 = document.getElementById("btn2");
  var btn3 = document.getElementById("btn3");
  btn1.onclick = function() {
    if(event.target == event.currentTarget){
       console.log(1)
    }
  }
  btn2.onclick = function() {
    if(event.target == event.currentTarget){
      console.log(2)
    }
  }
  btn3.onclick = function() {
    if(event.target == event.currentTarget){
      console.log(3)
     }
  }
</script>
</html>
<style>
  #btn1, #btn2, #btn3 {
    width: 500px;
    height: 300px;
    background-color: blue;
  }
  #btn2 {
    width: 300px;
    background-color: brown;
  }
  #btn3 {
    width: 200px;
    background-color: aquamarine;
  }
</style>

阻止默认事件

event.preventDefault( )
或者
return false

IE事件模型

IE的事件机制没有捕获阶段,事件流是非标准的,只有目标阶段冒泡阶段

<button id="btn">点我</button>
<script type="text/javascript">
  var target = document.getElementById("btn");
  target.attachEvent('onclick',function(){
    alert("我是button");
    alert(this) // window
  });
</script>

与之对应的也有事件的移除函数 :detachEvent()

阻止事件冒泡的方法:e = window.event;e.cancelBubble = true;

阻止默认事件发生:e = window.event; e.returnValue =false;

IE模型与标准事件模型的区别

  1. 由于IE不支持事件捕获,所以在注册函数中只有两个参数,类型和处理函数;
  2. 标准事件模型中,注册函数时,事件类型前不加on,IE中要加on;
  3. attachEvent注册的函数作为全局调用函数,this引用window对象;
// this指向的是当前的btn1
  btn1.addEventListener('click', function(){console.log(this)})
  1. 标准事件模型和IE事件模型都允许对同一元素,针对同一事件类型注册多个处理函数。但在标准事件模型中若注册同一函数,与之同名的函数都会被忽略,以第一个为准;在IE中,同一函数可以被注册多次,即发生次数等于注册次数。

由事件模型引发的概念

事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。

事件冒泡

微软提出了名为事件冒泡(event bubbling)的事件流。

事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。

事件捕获

网景提出另一种事件流名为事件捕获(event capturing)。与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。

事件代理

把原本绑定在子元素的事件委托给父元素,让父元素担任监听的任务。

// 点击父元素,监听父元素事件,当前点击的元素是不是包含了传递过来子元素的标签,包含的话就触发事件执行相关的函数。
// 通用的事件函数绑定 元素节点 绑定事件的类型 需要代理的元素 事件的处理 
function bindEvent(elem, type, selector, fn) {
if (!fn) { 
fn = selector;
selector = null; 
}  
elem.addEventListener(type, e => { 
const target = e.target// 当前点击的元素 
if (selector) { 
// 当前点击元素是不是包含了需要代理的元素标签 
// 代理绑定 
if(target.matches(selector)) { 
fn.call(target, e); 
} 
} else { 
// 触发自己 
fn.call(target, e); 
}  
}); 
}  

事件代理发生的时机

对于事件代理来说,在事件捕获或者事件冒泡阶段处理,并没有明显的优劣之分,但是由于事件冒泡的事件流模型,被所有主流的浏览器兼容,从兼容性角度来说还是建议大家使用事件冒泡模型

鼠标四个事件的区别 mouseover/mouseenter mouseleave/mouseout

  • mouseenter/mouseover 鼠标进入事件
  • mouseleave/mouseout 鼠标离开事件
  • mouseover mouseleave 会发生冒泡,当进入或者离开子元素也会触发事件
  • mouseenter mouseleave 只触发当前元素上的事件 如下例子可以帮助理解原理
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div class="inline">
    <div class="box out" onmouseout="out()">
      <span>out</span>
    </div>
    <div class="box leave" onmouseleave="leave()">
      <span>leave</span>
    </div>   
  </div>
  <div class="inline">
    <div class="box over" onmouseover="over()">
      <span>over</span>
    </div>
    <div class="box enter" onmouseenter="enter()">
      <span>enter</span>
    </div>
  </div>
</body>
</html>
<script>
// mouseover事件
function over() {
  console.log('触发了mouseover事件!');
}
// mouseenter事件
function enter() {
  console.log('触发了mouseenter事件!');
}  
// mouseout事件
function out() {
  console.log('触发了mouseout事件!');
}
// mouseleave事件
function leave() {
  console.log('触发了mouseleave事件!');
}
</script>
<!-- mounseenter只有在事件绑定的元素上触发,mouseover在子元素上也会触发 -->
<!-- mouuseleave只在元素上触发,mouseout在子元素上也会触发 -->
<style>
  .box {
    width: 200px;
    height: 200px;
    margin-bottom: 10px;
    background-color: aquamarine;
  }
  .over span,.enter span, .out span, .leave span {
    width: 50px;
    height: 50px;
    background-color: brown;
  }
  .enter span, .leave span {
    background-color: blue;
  }
  .inline {
    display: inline-block;
  }
</style>