[TOC]
* * * * *
> `JavaScript` 与 `HTML` 之间的交互是通过事件实现的。事件,就是用户或者浏览器自身执行的某种操作。`DOM` 规定了一些事件,`BOM` 也支持一些事件。在 `IE9` 之前,`IE` 对事件都有自己的一套解释,上面的都是遵循 `DOM` 的事件规范的
### 事件流
> 事件流描述的是从页面中接收事件的顺序,`IE` 的事件流是事件冒泡流,而 `Netscape` `Communicator` 的事件流是事件捕获流。
> `DOM2` 级事件规定事件流包含三个阶段:事件捕获阶段、处于目标阶段和时间冒泡阶段(实际中处于目标阶段包含在此阶段)
![](https://box.kancloud.cn/3b3a17798d9886441b4dccc9595a9e3d_467x277.png)
* * * * *
#### 事件冒泡
> 事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
~~~
以下面的 html 页面为例:
<html>
<head>
<title>click事件</title>
</head>
<body>
<div>click</div>
</body>
</html>
如果你单击了页面中的 `<div>` 元素,那么这个click事件会按照如下顺序传播:
` div -> body -> html ->document`
对于冒泡流的事件流机制,存在如下的兼容问题:
IE5.5 及更早版本: div -> body -> document
IE5.5 之后到IE9之前: div -> body -> html -> document
IE9、Firefox、Chrome 和 Safari:
div -> body -> html -> document -> window
~~~
~~~
<html>
<head>
<title>事件冒泡</title>
<meta charset="utf-8">
<body id='body'>
<div id="box">
<button id="my_btn">按钮</button>
</div>
<script>
function bodyz(){
alert('我是body');
}
function boxz(){
alert('我是div');
}
function btnz(event){
alert('我是input');
// event.stopPropagation();
// 阻止事件冒泡之后点击按钮就不会出现 我是div、我是body
}
var body=document.getElementById('body'),
box=document.getElementById('box'),
btn=document.getElementById('my_btn');
box.addEventListener('click',boxz,false);
btn.addEventListener('click',btnz,false);
body.addEventListener('click',bodyz,false);
</script>
</body>
</head>
点击按钮会依次弹出:我是input、我是div、我是body
~~~
* * * * *
#### 事件捕获
> 事件开始的时候由最不具体的节点接收,然后逐级向下传播到最具体的节点。事件捕获的用意在于在事件达到预定目标之前捕获它。以上面的实例来看,`click` 事件的执行顺序为:`document -> html -> body -> div`
> 虽然事件捕获是 `Netscape Communicator `唯一支持的事件流模型,但 `IE9` 、`Firefox、Chrome、Opera` 和 `Safari` 目前也都支持这种事件流模型。尽管“ `DOM2 `级事件”规范要求事件应该从 `document` 对象开始传播,但这些浏览器都是从 `window` 对象开始捕获事件的。由于老版本的浏览器不支持,因此很少有人使用事件捕获。建议大家放心地使用事件冒泡,在有特殊需要时在使用事件捕获。
>
* * * * *
#### `DOM` 事件流
> `DOM2` 级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供机会。然后实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
* * * * *
### 事件处理程序
#### `html` 事件处理程序
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script>
function showMessage(){
alert("HTML 事件处理程序");
}
</script>
<title>HTML 事件处理程序</title>
</head>
<body>
<button onclick="alert('HTML 事件处理程')">按钮1</button>
<button onclick="showMessage()">按钮2</button>
</body>
</html>
~~~
![](https://box.kancloud.cn/9c573fbdfb694facde0f61cf3bd6e91c_702x209.png)
![](https://box.kancloud.cn/6c327fbe89f98497f0a5d6b75fa1d71c_134x51.png)
>[danger] 在 HTML 中指定事件处理程序有两个缺点:
~~~
1. 存在一个时差问题。因为用户可能会在 HTML 元素出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。
2. HTML 与 JavaScript 代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML 代码和 JavaScript 代码。而这正是许多开发人员放弃 HTML 事件处理程序,转而使用 JavaScript 指定事件处理程序的原因所在。
3. 如果处理函数是在绑定的位置之后解析的,那么就会报错,所以最好使用 try{} catch() {} 语句块
~~~
* * * * *
#### `DOM0` 级事件处理程序
> 通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现的,而且至今仍然为所有现代浏览器所支持。原因一是简单,二是具有跨浏览器的优势。
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DOM0级事件处理程序</title>
</head>
<body>
<button id="my_btn">按钮</button>
<script>
var btn=document.getElementById('my_btn');
btn.onclick=function(){
// 这里可以使用 this 访问到元素的任何属性和方法
console.log('DOM0级事件处理程序');
setTimeout(function(){
btn.onclick=null; // 删除事件处理程序
console.log('删除事件处理程序');
},200);
}
</script>
</body>
</html>
~~~
以这种方式添加的时间处理程序会在时间流的冒泡阶段被处理
* * * * *
#### `DOM2` 级事件处理程序
> “ `DOM2` 级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:`addEventListener()` 和 `removeEventListener()`。所有的 `DOM` 节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 `true` ,表示在事件捕获阶段调用事件处理程序;如果是 `false` ,表示在冒泡阶段调用事件处理程序。
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DOM2级事件处理程序</title>
</head>
<body>
<button id="my_btn">按钮</button>
<script>
var btn=document.getElementById('my_btn');
btn.addEventListener('click',function(){
// click不要on,三个参数。
alert('DOM2级事件处理程序1');
},false);
btn.addEventListener('click',function(){
alert('DOM2级事件处理程序2');
},false);
// 先输出DOM2级事件处理程序1后 DOM2级事件处理程序2
</script>
</body>
</html>
~~~
>[danger] `DOM0` 和 `DOM2` 级事件处理程序都有一个共同的优点就是可以同时添加多个事件处理程序。使用 `removeEventListener()` 移除 `addEventListener()` 事件时,移除时传入的参数与添加处理程序时使用的参数相同。如果添加的是匿名函数,那么将无法移除
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DOM2级事件处理程序</title>
</head>
<body>
<button id="my_btn">按钮</button>
<script>
var btn=document.getElementById('my_btn');
var handler=function(){
alert('DOM2级事件处理程序');
setTimeout(function(){
btn.removeEventListener('click',handler,false);
alert('删除事件处理程序');
},1000);
};
btn.addEventListener('click',handler,false);
</script>
</body>
</html>
~~~
> 大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。
* * * * *
#### IE事件处理程序(现代浏览器可忽略)
> 高程3:“ `IE8` 是最后一个仍然使用其专有事件系统的主要浏览器,`IE9` 开始就实现了 `DOM2` 级事件的核心部分”`IE` 实现了与 `DOM` 中类似的两个方法:`attachEvent() `和 `detachEvent()` 。
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>IE事件处理程序</title>
</head>
<body>
<button id="my_btn">按钮</button>
<script>
var btn=document.getElementById('my_btn');
btn.attachEvent('onclick',function(){
// click要加on,两个参数。
alert('IE事件处理程序1');
});
btn.attachEvent('onclick',function(){
alert('IE事件处理程序2');
});
// 先输出IE事件处理程序2后IE事件处理程序1.
</script>
</body>
</html>
使用detachEvent()移除attachEvent()事件时,移除时传入的参数与添加处理程序时使用的参数相同。
~~~
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>IE事件处理程序</title>
</head>
<body>
<button id="my_btn">按钮</button>
<script>
var btn=document.getElementById('my_btn');
var handler=function(){
alert('IE事件处理程序');
setTimeout(function(){
btn.detachEvent('onclick',handler);
alert('删除事件处理程序');
},1000);
};
btn.attachEvent('onclick',handler);
</script>
</body>
</html>
~~~
* * * * *
#### 跨浏览器的事件处理程序(现代浏览器可忽略)
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>跨浏览器的事件处理程序</title>
</head>
<body>
<button id="my_btn">按钮</button>
<script>
var EventUtil={
addHandler: function(element, type, handler){
if(element.addEventListener){ // DOM2级
element.addEventListener(type,handler,false);
}else if(element.attachEvent){ // IE
element.attachEvent('on'+type,handler);
}else{
element['on'+type]=handler; // DOM0级
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){ // DOM2级
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){ // IE
element.detachEvent('on'+type,handler);
}else{
element['on'+type]=null; // DOM0级
}
}
}
var btn=document.getElementById('my_btn');
var handler=function(){
alert('跨浏览器的事件处理程序');
setTimeout(function(){
EventUtil.removeHandler(btn,'click',handler);
alert('删除事件处理程序');
},1000);
};
EventUtil.addHandler(btn,'click',handler);
// 事件类型不要加"on"
</script>
</body>
</html>
~~~
* * * * *
### 事件对象
~~~
参数设置方式
var btn=document.getElementById('my_btn');
btn.onclick=function(event) {}
btn.addEventListener('click', function(event) {})
~~~
~~~
实用属性/方法:
1. .currentTarget 只读 其事件处理程序当前正在处理事件的那个元素(事件程序冒泡或捕获的过程中,上下文所处的元素);
2. .target 只读 事件真正 被触发的点;
3. .defaultPrevented readonly
true--已经调用了 preventDefault() false--没有调用
4. .preventDefault() DOM3(新增) 如果 cancelable==true 则可执行该方法
5. .stopImmediatePropagation()
取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用
6. .stopPropagation() 取消进一步捕获或冒泡,需要 bubbles==true
7. .type 被触发事件的类型(点击后者焦点等)
~~~
>[warning] 只有事件处理程序执行期间,`event` 对象才会存在;一旦事件处理程序执行完,该对象就会被销毁
### 模拟事件
~~~
事件,就是网页中某个特别值得关注的瞬间。
DOM2中事件类型名是复数,DOM3中变成了单数。
1. UIEvents: 一般化的UI事件
2. MouseEvents: 一般化的鼠标事件
3. MutationEvents:一般化的DOM变动事件
~~~
~~~
1. 例子:模拟一个鼠标事件
var btn=document.getElementById('myBtn');
var event=document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, document.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
btn.dispatchEvent(event);
这里接受15个参数,都是鼠标事件中存在的
~~~
![](https://box.kancloud.cn/e5f4bb9ef6c383895b168c308bae4381_1043x637.png)
~~~
2. 模拟一个自定义事件(DOM3),可能需要进行能力测试
document.implementation.hasFeature('CustomEvents', '3.0')
~~~
![](https://box.kancloud.cn/68b538a9d8e54ec438bac833190f7906_556x53.png)
接受4个参数
![](https://box.kancloud.cn/602528c3b4a1d21fd2925ce284600585_739x145.png)