# JavaScript 事件流

## 定义

> 申明：本文仅限私人学习之用---- [原文](http://www.cnblogs.com/blackwood/archive/2013/03/14/2959195.html)；

> 下图展示了完整的 js 事件流

![tp](/images/js/2018_11_02/o_eventpic.jpg)

#### 捕获阶段：Document==>ElementHtml==>ElementBody==>ElementDiv

#### 冒泡阶段：ElementDiv==>ElementBody==>ElementHtml==>Document

## 分析

> #### 捕获阶段：
>
> document 对象首先接收到 click 事件，然后事件沿 DOM 树依次向下，一直传播到事件的实际目标。<br/>

> #### 冒泡阶段：
>
> IE 的事件流叫做事件冒泡（event bubbling），即事件开始时由最具体的元素（文档中嵌套层次最深的那个节点）接收，然后逐级向上传播到较为不具体的节点（ 文档）。

## 事件处理程序

### html 事件处理程序

> #### 事件对象：

    event:不是自己定义的，是給元素綁定事件之后，自动生成的局部变量，表示事件对象。
    this:在函數內部，等于事件的目标元素；在函數內部可以像访问局部变量一样访问document及该元素本身的成员。

```js
//如果当前元素是一個表单的輸入元素，则作用域中还会包含访问表单元素（父元素）的入口
(function() {
  with (document) {
    with (this.from) {
      //元素属性值
    }
  }
})();
```

#### 缺点：

    1.时差问题，用戶可能会在HTML元素一出現在页面上时，就触发相应事件，
      但当时该事件有可能不具备执行条件。
    2.这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。
      不同JavaScript引擎遵循的标识符解析规则略有差异，
      很可能会在访问非限定对象成员时出错。(高耦合)

### DOM0 级事件处理程序

> 通过 js 指定事件处理程序的传统方式，就是将一个函数赋值给一个事件处理程序属性

```js
//创建
var btn = document.getElementById('myBtn');
btn.onclick = function() {
  alert('Clicked');
};
//删除
btn.onclick = null;
```

### DOM2 级事件处理程序

#### addEventListener()

> 为指定事件添加处理程序

```js
// 下面这段程序中因为添加了false，所以该事件会在冒泡阶段被触发
var btn = document.getElementById('myBtn');
btn.addEventListener(
  'click',
  function() {
    alert('this.id');
  },
  false,
);
// 使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。
// 这两个事件处理程序会按照添加它们的顺序触发，因此首先会显示元素的ID，其次会显示" Hello world!" 消息。
var btn = document.getElementById('myBtn');
btn.addEventListener(
  'click',
  function() {
    alert('this.id');
  },
  false,
);
btn.addEventListener(
  'click',
  function() {
    alert('Hello World！');
  },
  false,
);
```

#### removeEventListener()

> 删除事件处理程序

```js
// 下面这个函数，虽然看起来合乎逻辑，
// 但是removeEventListener清除的程序与addEventListener绑定的事件不是同一个
var btn = document.getElementById('myBtn');
btn.addEventListener(
  'click',
  function() {
    alert('Hello World！');
  },
  false,
);
btn.removeEventListener('click', function() {
  alert('Hello World!');
});
// 我们在这里用另一个例子来表现,这个例子中绑定事件程序与删除事件程序使用了相同的函数
var btn = document.getElementById('myBtn');
var handler = function() {
  alert(this.id);
};
btn.addEventListener('click', handler, false);
btn.removeEventListener('click', handler, false);
```

### 建议

> 大多数情况下，都是将事件处理程序添加到事件流的冒泡阶段，这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要，我们不建议在事件捕获阶段注册事件处理程序。

## IE 事件处理程序

> IE 实现了与 DOM 中类似的两个方法：attachEvent()和 detachEvent()。这两个方法接受相同的两个参数：事件处理程序名称与事件处理程序函数。由于 IE8 及更早版本只支持事件冒泡，所以通过 attachEvent()添加的事件处理程序都会被添加到冒泡阶段。

### attachEvent()

```js
/* 下面这个例子中 事件绑定 时 ,第一个参数用的是 onclick，
IE使用attachEvent()与使用DOM0方法的主要区别在于处理程序的作用域。*/
var btn = document.getElementById('MyBtn');
btn.attachEvent('onclick', function() {
  alert('Clicked');
});
/* 在DOM0级方法的情况下，事件处理程序会在其所属元素的作用域内运行；
在使用attachEvent()方法的情况下，事件处理程序会在 全局作用域中运行，
因此this等于windows。 */
var btn = document.getElementById('myBtn');
btn.attachEvent('onclick', function() {
  alert(this === window); //true
});
// 添加多个元素的方法
var btn = document.getElementById('myBtn');
var handler = function() {
  alert('Hello World!');
};
var hello = function() {
  alert('hello');
};
btn.attachEvent('onclick', handler);
btn.attachEvent('onclick', hello);
// 执行顺序:多事件绑定，执行顺序是以倒序的方式执行的。
// hello
// Hello World!
```

### detachEvent()

```js
// 移除attachEvent添加的事件处理程序程序
var btn = document.getElementById('myBtn');
var handler = function() {
  alert('Clicked');
};
btn.attachEvent('onclick', handler);
btn.detachEvent('onclick', handler);
```

## 跨浏览器的事件处理程序

### addHandler()|removeHandler()

> 这 2 个方法属于一个名叫 EventUtil 的对象<br/>
> 参数：要操作的元素 | 事件名称 | 事件处理接受程序

```js
/* 解析：首先判断是否存在DOM2级方法，如果存在==>传入事件类型，
事件处理程序函数，和第三个参数方法false（表示冒泡阶段），
如果存在的是IE的方法，就采用第二种方法，为了兼容IE8及跟早的版本，
此时的时间类型必须加上"on"前缀。
最后一种是DOm0级方法(现代浏览器，一般不会执行这里的代码)，
使用方括号语法将属性名指定为事件处理程序或者将属性设置为null；*/

var EventUtil = {
  addHandler: function(element, type, handler) {
    if (element.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent('on' + type, handler);
    } else {
      element['on' + type] = handler;
    }
  },
  removeHandler: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(element, type, false);
    } else if (element.detachEvent) {
      element.detachEvent('on' + type, handler);
    } else {
      element['on' + type] = null;
    }
  },
};
// 实例：使用EventUtil对象==>绑定click事件并删除click事件
// 分析：由于addHandler和removeHandler没有考虑到所有浏览器的问题，
// 例如IE浏览器中的作用域的问题，所以我们往往只用作添加处理程序或者移除处理程序。
//提示：只DoM0级的浏览器已经不多了，所以需要注意的问题不是很多
var btn = document.getElementById('myBtn');
var handler = function() {
  alert('你好！');
};
EventUtil.addHandler(btn, 'click', handler);
EventUtil.removeHandler(btn, 'click', handler);
```
