导语:在js中,具有交互性程序的是事件驱动。它可以很好的和用户进行互动,增强网页的趣味性和互动效果,提高更好的用户体验。今天我分享一篇有关事件驱动的文章,讲诉事件的来龙去脉,文章有不妥之处,还请电邮反馈。
# 事件的起源
这节开始说事件的起源,也就是原始事件模型。这是最初始的事件处理模式,通常把它当作0级DOM的一部分,所有游览器都支持。
# 事件以及事件类型
事件就是当用户在用鼠标或者键盘对网页进行操作时所,触发了的网页调用某个方法,反馈给用户的结果。不同的事件类型,生成的事件也不一样,反馈效果也不一样。
在原始事件模型中,事件不能被js直接操作,是游览器从内部提取的。这些事件类型都是响应事件调用时的事件句柄名称。HTML属性就可以用来处理这些事件代码。
下面是收集总结的事件句柄列表:
序号|事件句柄|触发条件|支持元素--|---|---|---
1|onload|文档加载完毕|<body>
2|unonload|文档卸载完毕|<body>
3|onresize|调整窗口大小|<body>
4|onabort|图像加载被中断|<img>
5|onerror|图像加载发生错误|<img>
6|onclick|鼠标按下被释放|大多数元素
7|ondbclick|双击鼠标|大多数元素
8|onmousedown|鼠标键被按下|大多数元素
9|onmousemove|鼠标移动|大多数元素
10|onmouseup|释放鼠标键|大多数元素
11|onmouseover|鼠标移到元素上|大多数元素
12|onmouseout|鼠标离开元素|大多数元素
13|onkeydown|键盘被按下|表单元素、<body>
14|onkeyup|键盘被按下后释放|表单元素、<body>
15|onkeypress|键盘按下被释放,返回false
取消默认|表单元素、<body>
16|onblur|元素失去焦点|<body><input><textarea><button><select>
17|onfocus|元素得到焦点|<body><input><textarea><button><select>
18|onchange|得到焦点使值发生了改变|<input><textarea><select>
19|onselect|选中表单文本|<input><select><textarea>
20|onreset|表单重置,返回false
取消|<form>
21|onsubmit|表单提交,返回false
取消|<form>
以上是一些定义的事件列表,仔细点,你会发现列表中可以大致的分为鼠标事件、键盘事件、表单事件以及其他事件。后面会详细介绍。
# html属性的事件
之前说过,事件是用户在对html标签进行触发时调用的js方法,用来执行一些相关的任务。
所以在html中,以上列表的事件可以被当作html的属性来使用。这里部分大小写,但是我还是习惯小写。
【例如】:给一个按钮加上事件,弹出内容。
<button onclick="alert('你好啊,事件!');">点我</button>
# js属性的事件
如果不想再html属性值里面写js的方法,字符串,可以在js中写这样一个方法,然后在html属性中进行调用,同样也可以的。
【例如】:给一个输入框加上提示。
<input type="text" name="username" onchange="tips(event);">
function tips(event) {
var val = event.target.value;
var nameReg = /^[\u4e00-\u9fa5]{0,}$/;
if (!nameReg.test(val)) {
alert('请输入中文!');
}
}
2
3
4
5
6
7
还可以这样写。
<input type="text" name="username">
var username = document.getElementsByName('username')[0];
username.onchange = tips;
function tips(event) {
var val = event.target.value;
var nameReg = /^[\u4e00-\u9fa5]{0,}$/;
if (!nameReg.test(val)) {
alert('请输入中文!');
}
}
// 或者这样
username.onchange = function (event) {
var val = event.target.value;
var nameReg = /^[\u4e00-\u9fa5]{0,}$/;
if (!nameReg.test(val)) {
alert('请输入中文!');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# js事件的返回值
大多数情况下,事件都有返回值,如果是true就执行,不是就取消。
比如说:onclick事件,如果返回true就执行,否则不执行。
【例如】:提交表单不执行默认提交方法。
<form name="login" onsubmit="return checkForm();">
<input type="text" name="username">
<input type="submit" value="提交">
</form>
2
3
4
function checkForm() {
return false;
}
2
3
其实还有表单重置事件也是这样,你可以私下里去练习一下。
# this关键词
js中的this是一个很神奇的存在,一般情况下,它都指向的是Window顶层全局对象,也包括函数调用。
【例如】:你打印一下这个this,你会发现结果是Window。
console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
function hello() {
console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
}
hello();
2
3
4
5
但是也有其他情况,比如说对象方法调用,事件调用。这种情况下,就是谁调用,指向谁。对象里面的方法调用会指向对象本身。
【例如】:
1.对象方法调用
//对象方法调用
var obj = {
name: 'mark',
sayName: function () {
console.log(this); // {name: "mark", sayName: ƒ}
console.log(this.name); // mark
return this.name;
}
}
console.log(obj.sayName()); // mark
2
3
4
5
6
7
8
9
10
注意:如果把对象中的方法赋予另一个变量,由于这个变量是Window下的变量、属性,所以再次打印this会指向Window对象。
//对象方法调用
var obj = {
name: 'mark',
sayName: function () {
console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
}
}
var fn = obj.sayName;
console.log(fn());
2
3
4
5
6
7
8
9
2.事件调用
<button id="tip">点我</button>
//事件方法调用
var tip = document.getElementById('tip');
tip.onclick = function(event) {
console.log(this); // <button id="tip">点我</button>
console.log(this.id); // tip
}
2
3
4
5
6
# 作用域
在js中,有变量作用域,有函数作用域,但是所有作用域的顶层都是全局对象。
在事件句柄中,也存在作用域,但是这个作用域只是适用于html属性的事件句柄。
例如:在一个button按钮中调用了名为form的变量,那么就会解析成form属性。
<form>
<input type="text" name="username" value="hello">
<button onclick="alert(this.form.username.value);">点我</button>
</form>
2
3
4
在此案例中,当点我按钮点击后会把form解析成这个表单属性,从而获取到旁边输入框的值hello。
# 事件的2级DOM标准
在经历了原始事件模型后,DOM又进行了扩展,定义了一些高级的事件处理方法。这些方法除了ie游览器外其他的游览器都支持。ie有自己的处理方法,这些方法会在第三节进行补充。
# 事件传播
在0级DOM事件模型中,是游览器负责把事件分配到发生事件的元素,如果那个元素有合适的事件类型就会触发,不会再执行其他的操作。
但是2级DOM标准中规定的是只要事件发生在文档元素(目标元素)上,就会触发。它的上级元素也有机会触发该事件。
此时就有了事件传播,它主要是分为3个阶段进行。
- 事件传播的捕捉阶段(capturing)
这个阶段事件从Document对象沿着DOM树向下传播给那个目标节点。
这个阶段是从上到下进行传播,如果在途中有目标节点的祖先注册了捕捉事件句柄,就会先执行这个事件。
- 事件传播的第二个阶段(self)
这个阶段已经到达了目标元素本身,直接注册在元素本身的事件就会执行。
- 事件传播的起泡阶段(bubbling)
这个阶段,目标元素已经执行完自身的事件句柄了,所以这个阶段的事件就会沿着DOM树向上回到Document对象。如果在途中有目标节点的祖先有合适的事件句柄,就会先执行这个事件。
但是并非所有的事件类型都起泡,像表单这种form元素执行完自身事件后再往Document对象走没有意义。
一般来说,原始事件起泡,而高级语义的事件不起泡。
下面一张表就列出了哪些适用于哪个阶段的事件传播。
序号|事件类型|接口|B(起泡)|C(捕捉)|支持元素/属性
1|abort|Event|yes|no|<img>
2|blur|Event|no|no|<a><area><button><input><select><textarea>
3|change|Event|yes|no|<input><select><textarea>
4|click|MouseEvent|yes|yes|screenX,screenY,clientX,clientY,altKey,ctrlKey
5|error|Event|yes|no|<body>,<frameset><img>
6|focus|Event|no|no|<a><area><button><input><select><textarea>
7|load|Event|no|no|<body><frameset><iframe><img>
8|mousedown|MouseEvent|yes|yes|screenX,screenY,clientX,clientY,altKey,ctrlKey
9|mousemove|MouseEvent|yes|no|screenX,screenY,clientX,clientY,altKey,ctrlKey
10|mouseout|MouseEvent|yes|yes|screenX,screenY,clientX,clientY,altKey,ctrlKey
11|mouseover|MouseEvent|yes|yes|screenX,screenY,clientX,clientY,altKey,ctrlKey
12|mouseup|MouseEvent|yes|yes|screenX,screenY,clientX,clientY,altKey,ctrlKey
13|reset|Event|yes|no|<form>
14|resize|Event|yes|no|<body><iframe><frameset>
15|scroll|Event|yes|no|<body>
16|select|Event|yes|no|<input><textarea>
17|submit|Event|yes|yes|<form>
18|unload|Event|no|no|<body><frameset>
19|DOMActivate|UIEvent|yes|yes|detail
20|DOMFocusIn|UIEvent|yes|no|none
21|DOMFocusOut|UIEvent|yes|no|none
温馨提示:
在事件传播的过程中,任何事件句柄都可以使用表示事件的Event对象提供的stopPropagation()
方法来停止事件的传播。
也有一些事件会引起游览器对元素的默认动作,比如a标签,默认动作是超链接跳转。在事件传播阶段结束后,便会进行默认动作。可以使用Event对象提供的preventDefault()
方法来阻止默认动作的发生。
在0级DOM模型中,只能为特定对象的特定类型的事件注册一个事件句柄,但是在2级模型中,就可以为特定对象的特定类型的事件注册多个事件句柄。
# 事件句柄注册
在0级DOM事件中,通过html属性或者js来为元素注册事件;但在2级DOM事件中,可以调用addEventListener()方法来为某个元素注册事件,该方法有3个参数。
- 第一个参数是事件类型,不加前缀on,比如:
onmouseover
要写成mouseover
; - 第二个参数是句柄函数,也就是事件发生时调用的函数;这个函数只接受唯一的Event对象;
- 第三个参数是布尔值,值为true,那就用于捕捉事件,否则就是直接发生在元素本身的事件;
这个注册方法只对注册的元素有用,是独立的,可以为一个元素注册多个事件而互相不受影响的。
【例如】:
- 为按钮注册一个点击事件。
<button id="clickme">点我</button>
var clickme = document.getElementById('clickme');
clickme.addEventListener('click',clickMe,false);
function clickMe(e) {
console.log(this.id); // clickme
}
2
3
4
5
- 监听一个元素中发生的所有鼠标移入事件
<button id="overme">点我</button>
var overme = document.getElementById('overme');
overme.addEventListener('mouseover',handlerMouse,true);
function handlerMouse(e) {
console.log(this.id); // clickme
}
2
3
4
5
既然有注册,也就有注销事件,与之对应的是方法是removeEventListener()
,它和注册方法接受的参数一样,不过是从元素上面移除了这个事件。
var overme = document.getElementById('overme');
overme.removeEventListener('mouseover',handlerMouse,true);
function handlerMouse(e) {
console.log(this.id); // clickme
}
2
3
4
5
小提示:这个注册方法中第二个参数的函数内部的this是指向注册事件的元素。
# 事件模块
在2级DOM中,事件是模块化的,所以你可以使用以下方法来测试游览器是否支持2级DOM事件模块。
下面是我封装的一个方法,只需要传入两个参数就可以查询是否支持。
function searchDomSupport(name,version) {
if (document.implementation &&
document.implementation.hasFeature &&
document.implementation.hasFeature(name,version)
) {
return true;
} else {
return false;
}
}
var res = searchDomSupport('Event','2.0');
console.log(res); // true;
2
3
4
5
6
7
8
9
10
11
12
# Event接口和Event
2级DOM API提供了事件发生时事件的一些额外信息,包括事件发生的时间,类型,元素的属性等等。事件是模块化的,所以一个模块就有一个相关的事件接口,声明了该事件类型的详细信息。
Event接口就是这个事件接口,它包括以下几部分:
序号|模块名|事件接口|事件类型 1|HTMLEvents|Eevent|abort,blur,change,load,resize,scroll,select 2|MouseEvents|MouseEvent|click,mousedown,mouseup,mouseover,mouseout 3|UIEvents|UIEvent|DOMActivate,DOMFocusIn,DOMFocusOut
# Event的属性
- type,发生事件的类型,和注册事件名称一样,比如:click,mouseover;
- target,发生事件的节点信息;
- currentTarget,发生当前事件的节点,在事件传播过程中和target的值不一样;
- eventPhase,一个数字表明当前所处的传播阶段,是一个常量;
- timeStamp,一个Date()对象,表面事件发生的时间;
- bubbles,一个布尔值,声明该事件是否在文档中起泡;
- cancelable,一个布尔值,声明该事件是否能用preventDefault()方法;
【例如】:获取按钮的点击事件的属性。
<button id="tips">点我</button>
var tips = document.getElementById('tips');
tips.onclick = function (e) {
var e = e || window.event;
console.log(e.type); // click
console.log(e.target); // target: button#tips
console.log(e.currentTarget); // null
console.log(e.eventPhase); // 2
console.log(e.timeStamp); // 3006.099999998696
console.log(e.bubbles); // true
console.log(e.cancelable); // true
}
2
3
4
5
6
7
8
9
10
11
# Event的方法
stopPropagation()
,阻止事件从当前正在处理它的节点传播;preventDefault()
,阻止游览器执行节点的默认动作;
【例如】:
// 调用此函数
function clickMe(e) {
e.stopPropagation();
}
//超链接跳转禁用
function clickMe(e) {
e.preventDefault();
}
2
3
4
5
6
7
8
# UIEvent的属性
- view,发生事件的Window对象;
- detail,对于鼠标点击事件来说,值为1就是点击1次,为2就是点击2次。如果值为2,说明之前还有一个点击为1的事件;
# MouseEvent的属性
- button,一个数字,声明在mousedown,mouseup,click事件中,哪个鼠标键改变了状态。0为左键,1为中间键,2为右键;
- altKey、ctrlKey、metaKey、shiftKey,这四个值都是布尔值,声明在鼠标事件发生时,是否同时按住了这四个键盘键;alt,ctrl,shift,meta;
- clientX、clientY,声明鼠标指针位于游览器窗口的X坐标和Y坐标;不考虑文档滚动,如果在顶部则clientY始终是0;在ie以外,要转换成文档坐标,而不是游览器窗口坐标;可以使用window.pageXOffset和window.pageYOffset来获取。
- screenX、screenY,声明鼠标指针位于用户显示器的左上角的X坐标和Y坐标;
- relatedTarget,引用事件的节点相关的节点。比如:对于mouseover,是鼠标移入目标节点离开时的那个节点;
【例如】:按钮的点击事件属性
<button id="tips">点我</button>
var tips = document.getElementById('tips');
tips.onclick = function (e) {
console.log(e.button); // 0
console.log(e.altKey,e.ctrlKey,e.metaKey,e.shiftKey); // false false false true
console.log(e.clientX,e.clientY); // 22 17
console.log(e.screenX,e.screenY); // 22 119
console.log(e.relatedTarget); // null
}
2
3
4
5
6
7
8
温馨提示:2级DOM事件模型是可以兼容0级DOM事件的一些属性和方法。这叫混合事件模型。
# IE事件模型
本节介绍的是IE4、5、6支持的中间模型,它介于0级模型和2级DOM事件模型之间。包括以下几部分:
Event对象,和2级DOM事件模型的Event属性有些相似;
IE事件的传播、注册和内存泄漏
# Event对象的属性
- type,发生事件的类型,和2级DOM中的type属性兼容。比如:click,mouseover;
- srcElement,发生事件的文档元素,和2级DOM中的target属性兼容;
- button,声明鼠标被按下的鼠标键;值为1是左键,2为右键,4为中间键;
- clientX、clientY,声明鼠标指针位于游览器窗口的X坐标和Y坐标;要转换成文档坐标,需要加文档滚动的量;
- offsetX、offsetY,声明鼠标指针位于元素的位置;
- altKey、ctrlKey、shiftKey,这四个值都是布尔值,声明在鼠标事件发生时,是否同时按住了这三个键盘键;
- keyCode,声明在键盘事件(keydown,keyup,keypress)中的键盘码;可以用String.fromCharCode()方法把字符代码转换成字符串;
- fromElement、toElement,fromElement声明mouseover事件移动过的文档元素,toElement声明mouseout事件移到的文档元素;
- cancelBubble,布尔值为true时阻止当前事件进一步起泡到上一层次的元素;相当于2级DOM中的stopPropagation()方法;
- returnValue,布尔值为false时可以阻止游览器执行默认动作,相当于2级DOM中的preventDefault()方法;
【例如】:在ie游览器中进行点击事件。
<button id="tips">点我</button>
var tips = document.getElementById('tips');
tips.onclick = function (e) {
console.log(e.type); // click
console.log(e.srcElement); // <button id="tips">点我</button>
console.log(e.button); // 0
console.log(e.clientX,e.clientY); // 30.399999618530273 14.399999618530273
console.log(e.offsetX,e.offsetY); // 29.399999618530273 11.800000190734863
console.log(e.altKey,e.ctrlKey,e.shiftKey); // false false false
console.log(e.cancelBubble); // false
console.log(e.returnValue); // undefined
}
2
3
4
5
6
7
8
9
10
11
温馨提示:IE中事件模型Event对象是作为全局的变量,在Window对象下的,所以要使用必须这样写window.event。
# IE事件注册
ie4中是和0级模型一样的方法;ie5以及以后的版本是使用attachEvent()方法和detachEvent()方法,类似于2级DOM事件模型中的事件监听注册方法;但是不同的是:
IE事件模型不支持事件捕捉,所以这两个方法的参数只要两个;
- 第一个参数的事件类型名称应该包括on这个前缀;例如:click要写成onclick;
attachEvent()
方法注册的函数将作为全局函数调用,而不是事件的目标元素,this关键词指向Window;
attachEvent()
方法允许注册多次,当事件发生时,注册函数的被调用次数和注册次数一样多;
事件起泡:IE事件模型中的事件起泡,想要阻止只能把cancelBubble
这个属性设置为true,当新事件生成时,又会自动还原成false。
事件捕捉:IE事件模型中的事件捕捉,可以使用setCapture()
和releaseCapture()
方法来实现,只是对鼠标事件适用。
内存泄漏:IE6之前的嵌套函数容易引起内存泄漏。
# IE兼容性方法
下面是我根据原始事件模型、2级DOM事件模型和IE事件模型,封装的一个方法。
var uniHandler= {};
if (document.addEventListener) {
uniHandler.add = function (elem,eventType,handleEvent,isCapture) {
elem.addEventListener(eventType,handleEvent,isCapture);
}
uniHandler.remove = function (elem,eventType,handleEvent,isCapture) {
elem.removeEventListener(eventType,handleEvent,isCapture);
}
uniHandler.stoppro = function () {
e.stopPropagation();
};
uniHandler.prevent = function () {
e.preventDefault();
}
} else if (document.attachEvent) {
uniHandler.add = function (elem,eventType,handleEvent) {
elem.attachEvent('on'+eventType,handleEvent);
}
uniHandler.remove = function (elem,eventType,handleEvent) {
elem.detachEvent('on'+eventType,handleEvent);
}
uniHandler.stoppro = function () {
e.cancelBubble = true;
};
uniHandler.prevent = function () {
e.returnValue = false;
}
} else {
uniHandler.eventOrigin = function (elem,eventType,handleEvent) {
elem.eventType = handleEvent;
}
}
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
【例如】:在ie和非ie游览器中测试,均没有问题;ie包括ie5-11。
一个按钮注册点击事件
<button id="tips">点我</button>
var tips = document.getElementById('tips');
uniHandler.add(tips,'click',function () {
alert('你好!');
},false);
2
3
4
一个链接注册点击事件,并且阻止游览器默认动作
<a id="goto" href="#111" target="_blank">链接</a>
var goto = document.getElementById('goto');
uniHandler.add(goto,'click',function (e) {
var e = e || window.event;
uniHandler.prevent(e);
alert('我不走!');
},false);
2
3
4
5
6
# 写在最后
有关DOM中的事件今天就说到这里,还有不明白的可以到前面去仔细看看,多练练,你就会了。