事件的监听以及处理

事件

事件,是文档或浏览器窗口中发生的一些特定的交互瞬间。

下面分别介绍事件中的几个重要特性:

  • 事件流
  • 事件处理程序
  • 事件对象

一、事件流

事件流是用于描述页面接收事件的顺序。

根据事件的捕获顺序不同,事件流分为2种:

  • 事件冒泡流
  • 事件捕获流

1.1 事件冒泡

事件冒泡,是指事件由最具体的元素(即最底层的节点)接收,然后逐级向上传播到较为不具体的节点(即最顶层的节点,文档),属于由下向上传播事件。

1
2
3
4
5
<html>
<body>
<div id="myDiv">Click</div>
</body>
</html>

如果点击了页面中的 div 元素,那么这个 click 事件会按照以下顺序传播:

  1. <div>
  2. <body>
  3. <html>
  4. document

所有浏览器都支持事件冒泡。

1.2 事件捕获

事件冒泡,是指事件由较不具体的元素(即最顶层的节点,文档)接收,然后逐级向下传播到最不具体的节点(即最底层的节点),属于由上到下传播事件。

1
2
3
4
5
<html>
<body>
<div id="myDiv">Click</div>
</body>
</html>

如果点击了页面中的 div 元素,那么这个 click 事件会按照以下顺序传播:

  1. document
  2. <html>
  3. <body>
  4. <div>

老版本的浏览器不支持事件捕获。

1.3 DOM 事件流

“DOM2级事件”规定的事件流包括3个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。

1
2
3
4
5
<html>
<body>
<div id="myDiv">Click</div>
</body>
</html>

以 div 的点击事件为例,事件流的顺序是:

  1. document(捕获)
  2. <html>(捕获)
  3. <body>(捕获)
  4. <div>(目标、冒泡)
  5. <body>(冒泡)
  6. <html>(冒泡)
  7. document(冒泡)

另外,在 DOM 事件流中,实际目标接收事件的时候不是处于捕获阶段,而是属于冒泡阶段。

也就是说,实际上只分为2个阶段:事件捕获阶段、事件冒泡阶段(包括处于目标阶段)。

二、事件处理程序

事件是用户或浏览器自身执行的某种动作,而事件处理程序就是响应某个事件的函数。

一般情况下,事件处理程序的名字以on开头,具体形式为“on” + 事件名

例如 click 事件的事件处理程序就是 onclickload 事件的事件处理程序就是onload

为事件指定事件处理程序的方式有几种:

  • HTML事件处理程序
  • DOM0级事件处理程序
  • DOM2级事件处理程序
  • IE事件处理程序

2.1 HTML事件处理程序

某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来执行:

1
<div onclick="alert('click')"></div>

例如 div 的click事件就可以用onclick来指定事件处理程序。

以这种方式来指定事件处理程序,有一些特别的性质:

  • 会创建一个封装着元素属性值的函数;
  • 事件处理程序的this值等于当前事件的目标元素;
  • 事件处理函数中有一个局部变量事件对象event,不需要自己定义,也不需要从函数的参数列表获取。
1
2
3
4
5
6
7
8
9
<script>
function handleClick (event) {
alert(event.type) //
}
</script>

<div value="Click" onclick="alert(this.value)"></div>
<!-- event 属性不需要定义,也不需要从参数列表中拿 -->
<div onclick="handleClick(event)"></div>

这样指定事件处理程序实际上会创建一个函数来执行指定的事件代码,差不多类似于以下代码:

1
2
3
4
5
6
7
function () {
alert(this.value)
}

function () {
handleClick(event)
}

但是这种指定的方式也有一些缺点:

  • 存在时差问题。由于事件处理程序是直接绑定在HTML元素上面的,因此有可能在HTML元素渲染时就触发了事件处理程序,但是当时的事件处理程序有可能还不具备执行条件,从而导致执行出现异常;
  • 在不同的浏览器中,事件处理程序的作用域链有可能存在一些差异,从而导致不同的结果;
  • HTML与Javascript代码紧密耦合,不方便后续的维护。

2.2 DOM0 级事件处理程序

由于用 HTML 来指定事件处理程序,会使得 HTML 代码和 Javascript 代码耦合,并且不好维护。

因此为了处理这种情况,可以只使用 Javascript 来指定事件处理程序,这样不仅简单,在跨浏览器上兼容也比较容易。

每个 DOM 元素(包括 window 和 document)都有自己的事件处理程序属性,通常都是全部小写,例如 onclick。把事件处理程序属性设置为一个函数,就可以指定事件处理程序:

1
2
3
4
var btn = document.getElementById("myBtn");
btn.onclick = function () {
alert("Click");
}

这种方式称为 DOM0 级方法指定事件处理程序。

这种方式有几个特点:

  • 事件处理程序中的 this 表示当前元素;
  • 事件处理程序会在事件流的冒泡阶段被处理。

删除 DOM0 级方法指定的事件处理程序也很简单,直接赋值为 null 就可以了:

1
2
// 删除事件处理程序
btn.onclick = null;

2.3 DOM2 级事件处理程序

DOM0 级方式指定事件处理程序时,只能指定一个事件处理程序,当需要多个处理程序对同一个对象的同一个事件进行处理时,没有办法做到。

因此在 DOM2 级中,添加了2个方法,用于处理指定和删除事件处理程序的操作:

  • addEventListener()
  • removeEventListener()

所有 DOM 节点都包含这2个方法,这2个方法都接收3个参数:

  • 要处理的事件名,比如 onclick
  • 指定的事件处理函数
  • 一个布尔值,true 表示在捕获阶段调用事件处理函数,false 表示在冒泡阶段调用事件处理函数。

以这种方式来指定事件处理程序,有几个特点:

  • 可以添加多个事件处理程序;
  • 事件处理函数中的 this 表示当前元素;
  • 移除事件处理程序时,必现传入和添加处理程序时使用的参数相同。也就是说,如果添加的事件处理程序是匿名函数,将无法对它进行移除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var btn = document.getElementById("myBtn");
var handler = function () {
alert(this.id);
};

// 添加匿名函数,意味着无法移除
btn.addEventListener("click", function () {
alert("Click");
}, false);

// 可以添加多个事件处理程序,按添加顺序执行
btn.addEventListener("click", handler, false);

// 移除事件处理时,传入的参数必须和添加时相同
btn.removeEventListener("click", handler, false);

2.4 IE 事件处理程序

和 DOM 中不同的是,IE 并没有实现 addEventListenerremoveEventListener,而是另外实现了和这2个方法功能相近的方法:

  • attachEvent()
  • detachEvent()

IE 的事件处理和 DOM2 的事件处理有几点区别:

  • IE8 以前只支持事件冒泡,所以 attachEvent 添加的事件处理只能在冒泡阶段被执行;
  • attachEvent 的事件参数是 onclick,而 addEventListener 的事件参数是 click,没有加上 on
  • attachEvent 的事件处理函数的作用域是全局作用域,而 addEventListener 的则是所属元素的作用域;
  • attachEvent 添加的事件处理程序是按添加顺序反序执行的,而 addEventListener 是按添加顺序执行的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var btn = document.getElementById("myBtn");
var handler = function () {
alert(this.id);
};

// 注意这里是 onclick,而不是 DOM 的 click
btn.attachEvent("onclick", function () {
// 在全局作用域下运行
alert(this === window); // true
}); // 没有第3个参数,事件处理只会在冒泡阶段执行

// 也可以添加多个事件处理程序,按添加顺序反序执行
btn.attachEvent("onclick", handler);

// 移除事件处理时,传入的参数必须和添加时相同
btn.detachEvent("onclick", handler);

ps:需要特别注意的就是,使用attachEvent绑定的事件处理程序的作用域比较特殊,它的this并不是代表当前所属元素。

2.5 跨浏览器事件处理程序

针对不同的浏览器,可以写出跨浏览器的方式处理事件。

跨浏览器事件处理只需要处理冒泡阶段,因为有些浏览器版本是不支持捕获事件的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
}

三、事件对象

在触发 DOM 上的某个事件时,都会产生一个事件对象 event,这个对象包含着所有与事件有关的信息。

事件对象包括导致事件的元素、事件类型以及其他相关信息。

所有浏览器都支持事件对象,但是不同浏览器有不同的实现。

针对不同的浏览器,事件对象 event 也有不同的实现:

  • DOM 中的事件对象
  • IE 中的事件对象

3.1 DOM 中的事件对象

对于不同的绑定事件处理程序的方式,获取事件对象的方式也不一样。

1)HTML 事件处理程序

1
2
<!-- click -->
<div onclick="alert(event.type)"></div>

在 HTML 指定事件程序这种方法中,事件对象 event 是保存在变量 event 中的,可以直接拿到。

2)DOM0/DOM2 级事件处理程序

1
2
3
4
5
6
7
8
9
10
11
var btn = document.getElementById("myBtn");

// 标准 DOM0 级
btn.onclick = function (event) {
alert(event.type); // "click"
}

// 标准 DOM2 级
btn.addEventListener("click", function (event) {
alert(event.type); // "click"
}, false);

对于 DOM2/DOM2 级指定事件处理程序的方式,event 对象可以直接在事件处理程序的参数中获取。

DOM 中的事件对象有一些特殊的属性和方法:

属性/方法 说明
bubbles 表明事件是否可以冒泡
cancelable 表明是否可以取消事件的默认行为
currentTarget 当前事件处理程序绑定的元素
defaultPrevented 为 true 表示已经调用过 preventDefault() 了
eventPhase 当前事件流阶段。1表示捕获阶段,2表示“处于目标”,3表示冒泡阶段
target 触发事件的元素
type 事件类型
preventDefault() 取消默认的事件行为。只有 cancelable=true 时才能生效
stopPropagation() 取消事件的进一步捕获或冒泡。只有 bubbles=true 时才能生效
stopImmediatePropagation() 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用

3.1.1 target

在事件处理程序内部,this 始终等于 currentTarget,而 target 则只是事件的目标对象(即触发事件的元素)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var btn = document.getElementById("myBtn");

// 触发事件的元素的事件处理
btn.onclick = function (event) {
alert(event.currentTarget === this); // true
alert(event.target === this); // true
}

// 捕获/冒泡阶段的事件处理
document.body.onclick = function (event) {
// currentTarget 执行的是当前事件处理程序的绑定元素
alert(event.currentTarget === this); // true
alert(event.currentTarget === document.body); // true

// target 始终指向的是触发事件的元素
alert(event.target === this); // false
alert(event.target === btn); // true
}

3.1.2 preventDefault()

为了阻止事件的默认行为,可以使用 preventDefault() 方法,但是要使用这个方法,必须确保 cancelable=true 才行,否则方法不会生效。

默认行为指的是 dom 元素的默认动作,比如 <a hrtf=""> 元素的默认行为就是在 click 触发时导航到 href 指定的 URL。使用 preventDefault() 可以阻止其跳转。

1
2
3
4
var link = document.getElementById("myLink");
link.onclick = function (event) {
event.preventDefault();
}

3.1.3 stopPropagation()

为了阻止事件继续在事件流里传播,可以使用 stopPropagation() 方法,但是使用这个方法前,要确保 bubbles=true 才行,否则方法不会生效。

1
2
3
4
5
6
7
8
9
10
11
var btn = document.getElementById("myBtn");

// 触发事件的元素的事件处理
btn.onclick = function (event) {
alert("btn click");
}

// 捕获/冒泡阶段的事件处理
document.body.onclick = function (event) {
alert("doc click");
}

正常情况下,由于事件的冒泡,当点击按钮时,会弹出2个警告框。

因此为了避免这种情况,可以使用 stopPropagation() 方法来阻止事件进一步传播:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var btn = document.getElementById("myBtn");

// 触发事件的元素的事件处理
btn.onclick = function (event) {
alert("btn click");

// 阻止事件继续传播
event.stopPropagation();
}

// 捕获/冒泡阶段的事件处理
document.body.onclick = function (event) {
alert("doc click");
}

这个时候,点击按钮时,只会弹出1个警告框。

但是 stopPropagation() 还存在一个问题,它只能阻止事件的冒泡,但是不能阻止同级事件处理程序的执行。前面说过,一个事件可以绑定多个事件处理程序,stopPropagation() 是没有阻止当前目标元素的其他事件处理程序的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var btn = document.getElementById("myBtn");

var handler1 = function () {
alert("handler1");

// 阻止事件继续传播
event.stopPropagation();
};
var handler2 = function () {
alert("handler2");
};

// 可以添加多个事件处理程序,按添加顺序执行
btn.addEventListener("click", handler1, false);
btn.addEventListener("click", handler2, false);

这个时候点击按钮,依旧会弹出2个警告框,因为stopPropagation()只是阻止了事件冒泡,但是同级事件处理程序还是会执行的。

所以在 DOM3 中,添加了一个新的方法stopImmediatePropagation(),它可以阻止后续的所有事件处理程序执行,包括同级别的事件处理程序。

3.2 IE 中的事件对象

IE 中的事件对象获取方式有些不同,它取决于在IE中指定事件处理程序的方法。例如:

在IE中使用DOM0级方法添加事件处理程序时,event 事件对象是作为 window 对象的一个属性存在的;

但是在 IE 中使用 attachEvent() 添加事件处理程序时,event 对象是作为事件处理函数的参数存在的。

1
2
3
4
5
6
7
8
9
10
11
12
var btn = document.getElementById("myBtn");

// IE 下的 DOM0 级
btn.onclick = function () {
var event = window.event;
alert(event.type); // "click"
}

// IE 下的 DOM2 级
btn.attachEvent("onclick", function (event) {
alert(event.type); // "click"
});

IE 中的事件对象有一些特殊的属性和方法:

属性/方法 说明
cancelBubble 用于是否取消事件冒泡,与stopPropagation()作用相同
returnValue 用于是否取消事件的默认行为,与preventDefault()作用相同
srcElement 当前事件处理程序绑定的元素,与target属性相同
type 事件类型

3.2.1 srcElement

事件处理程序的作用域是由指定它的方式来确定的,因此在写代码时需要特别注意,最好还是使用event.srcElement来替代``this`来作为当前目标对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
var btn = document.getElementById("myBtn");

// IE 下的 DOM0 级
btn.onclick = function () {
var event = window.event;
alert(event.srcElement === this); // true
}

// IE 下的 DOM2 级
btn.attachEvent("onclick", function (event) {
alert(event.srcElement === this); // false
alert(window === this); // true
});

3.3.2 retrunValue

IE 中取消事件默认行为的方法是使用returnValue=false,与preventDefault()作用相同:

1
2
3
4
5
var link = document.getElementById("myLink");
link.onclick = function () {
var event = window.event;
event.returnValue = false;
}

和标准 DOM 不同的是,IE 中没有方法确定事件是否可以被取消。

3.2.3 cancelBubble

IE 中阻止事件冒泡的方法是使用cancelBubble=true,与stopPropagation()作用相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var btn = document.getElementById("myBtn");

// 触发事件的元素的事件处理
btn.onclick = function () {
var event = window.event;
alert("btn click");

// 阻止事件继续传播
event.cancelBubble = true;
}

// 捕获/冒泡阶段的事件处理
document.body.onclick = function (event) {
alert("doc click");
}

点击按钮,只会弹出一个警告框。

3.3 跨浏览器的事件对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
getEvent: function (event) {
return event ? event : window.event;
},
getTarget: function (event) {
return event.target || event.srcElement;
},
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
stopPropagation: function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}

事件的监听以及处理

http://example.com/lang/js/events/

作者

jiaduo

发布于

2021-08-28

更新于

2023-04-02

许可协议