Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 事件流 #12

Open
felix-cao opened this issue Sep 3, 2018 · 0 comments
Open

JavaScript 事件流 #12

felix-cao opened this issue Sep 3, 2018 · 0 comments

Comments

@felix-cao
Copy link
Owner

felix-cao commented Sep 3, 2018

事件流描述的是从页面中接收事件传播(Event Propgation)的顺序,分为事件冒泡(Event Bubbling)和事件捕获(Event Capturing)两种形式,这两种形式分别是 IENetscape 提出的两种完全相反的事件流概念。其本质是谁当事件流的起点和终点的问题。

一、事件冒泡和事件捕获

事件流最早要从 IENetscape(即网景公司) 的浏览器大战说起,IE 提出的是事件冒泡流,而网景提出的是事件捕获流,后来在 W3C 组织的统一之下,JavaScript 支持了冒泡流和捕获流,但是目前低版本的IE浏览器还是只能支持冒泡流(IE6,IE7,IE8均只支持冒泡流)。

1.1、事件冒泡

事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),然后逐级传播到较为不具体的节点,是一种自下往上的顺序。
举个栗子,就很容易明白了。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Event Bubbling</title>
</head>
<body>
  <button id="clickMe">Click Me</button>
</body>
<script>
var button = document.getElementById('clickMe');

button.onclick = function() {
  console.log('1. You click Button');
};
document.body.onclick = function() {
  console.log('2. You click body');
};
document.onclick = function() {
  console.log('3. You click document');
};
window.onclick = function() {
  console.log('4. You click window');
};
</script>
</html>

在代码所示的页面中,如果点击了button,那么这个点击事件会按如下的顺序传播(Chrome浏览器):
button --> body --> document --> window
也就是说,click事件首先在button元素上发生,然后逐级向上传播。这就是事件冒泡。

1.2、事件捕获

与事件冒泡正好相反,当某个事件发生时,父元素应该更早接收到事件,具体元素则最后接收到事件,是一种自上而下的顺序。
事件捕获,为父元素截获事件提供了机会。
比如说刚才的demo,如果是事件捕获的话,事件发生顺序会是这样的:
window --> document --> body --> button

var button = document.getElementById('clickMe');

button.addEventListener('click', function() {
  console.log('1. You click Button');
}, true);
document.body.addEventListener('click', function() {
  console.log('2. You click body');
}, true);
document.addEventListener('click', function() {
  console.log('3. You click document');
}, true);
window.addEventListener('click', function() {
  console.log('4. You click window');
}, true);

addEventListener最后一个参数,为true则代表使用事件捕获模式,false则表示使用事件冒泡模式。

1.3、阻止事件冒泡

事件冒泡过程,是可以被阻止的。防止事件冒泡而带来不必要的错误和困扰。阻止冒泡的方法就是 stopPropagation()

button.addEventListener('click', function(event) {
  // event为事件对象
  console.log('1. You click Button');
  event.stopPropagation();
  console.log('Stop Propagation!');
}, false);

二、DOM 事件流

2.1、DOM0 级事件

DOM0 级事件处理方式就是将一个函数赋值给一个DOM树中某个节点的事件属性,如:

document.getElementById('box').onclick = function() {
  console.log('DOM:', this);
}

就是把函数赋值给了 id = box 的节点元素的 onclick 属性, 还有 onmousedownonmouseup 等事件属性。on 前缀这种事件绑定方法属于 DOM0 级事件绑定。

DOM0 级事件有三个特点: 🔢

  • 只能触发事件冒泡阶段不能触发事件捕获阶段。
  • 同一元素绑定相同的事件,后面的会覆盖前面的。也是缺点 👍
  • 函数内的 this 指的是事件流传播到的这个元素,即元素本身。

2.2、DOM2 级事件

2.2.1、流程

DOM2 级事件规定的事件流包括三个阶段: 🔢

  • 事件捕获阶段,Event Capturing Phase
  • 处于目标阶段,Target Phase
  • 事件冒泡阶段,Event Bubbling Phase

如下图:

2.2.2、两个方法

DOM2 级事件定义了两个方法用于处理添加事件处理程序和删除事件处理程序的操作:

  • addEventListener(event, function, useCapture)
  • removeEventListener(event, function, useCapture)

所有DOM节点都包含这两个方法,他们包含三个参数,第一个参数为事件类型;第二个参数为事件函数,第三个参数为布尔值,如果是true的话,说明是事件流是捕获事件,如果是false的话,那么事件流是冒泡事件;

IE 实现了也提供了类似的2个方法,分别是attachEvent()detachEvent(),这两个方法只接受2个参数,第一个参数是事件名称,第二个参数是要处理的函数;由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序会被添加到冒泡阶段;

下面是IE事件处理程序的代码如下:

btn.attachEvent('onclick',handler);
function handler(e){
    alert(this); // window
}

注意:attachEvent的事件名称是onclick,而addEventListener的事件名称是click,且IE中使用的attachEvent()与使用DOM0级方法的的主要区别在于事件处理程序的作用域,在使用dom0级情况下,事件处理程序在其所属元素的作用域内运行,在使用attachEvent()方法的情况下,事件处理程序在全局作用域下运行,其中的this等于window

2.2.3、DOM2 级事件绑定的特点

  • 对于精确的元素不区分事件捕获和事件冒泡,事件的执行顺序是按照绑定顺序来的。
  • 同一个元素绑定相同的事件,后面的不会覆盖前面的。因为DOM2级事件绑定不是给元素添加属性是直接添加的事件。等同于给一个元素绑定了多个事件。
  • DOM2级事件处理中通过addEventListener()添加的匿名函数无法移除,要先给函数命名。

三、判断事件所处的阶段

event 对象都有一个属性 eventPhase,表示调用事件处理程序的阶段,属性的值可以为1,2,3。 获取Event对象的target属性,代表当前的元素对象

<body>
<button id="btn">点击</button>
<script type="text/javascript">
var btn=document.getElementById("btn");
var eventPhase=["捕获阶段","目标阶段","冒泡阶段"];

document.body.addEventListener("click",function(event){
    var index=event.eventPhase;
    console.log("document click处于"+eventPhase[index-1]);//捕获阶段 1
},true);
btn.addEventListener("click",function(event){
    var index=event.eventPhase;
    console.log("button click处于"+eventPhase[index-1]);//目标阶段 2
},true);
document.body.addEventListener("click",function(event){
    var index=event.eventPhase;
    console.log("document click处于"+eventPhase[index-1]);//冒泡阶段 3
});

</script>
</body>

使用事件代理,给父节点绑定监听事件,可以提升性能,可以减少绑定给每个子节点, 停止事件冒泡,调用Event对象的stopPropagation()方法,降低事件的复杂性

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant