事件流与事件处理程序
js与HTML之间的交互是通过事件实现的。可以使用侦听器来预订事件,以便事件发生时执行相应的代码,这种模式被称为观察者模式。
1. 事件流
事件流描述的是从页面中接收事件的顺序。IE的事件流是事件冒泡流,而Netscape Communicator的事件流失事件捕获流。
事件捕获
由不太具体的节点更早接收到事件,而最具体的节点最后接收到事件。事件捕获的用意在于,在事件到达预定目标之前捕获它。1
document -> <html> -> <body> -> <div>
事件冒泡
即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。1
<div> -> <body> -> <html> -> document
2. 绑定事件
2.1 <div onclick="">
元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来制定。1
2
3
4
5<input type="button" onclick="alert('clicked!')">
<!-- onclick有权访问全局作用域中的任何代码 -->
<input type="button" onclick="showMessage()">
<input type="button" onclick="try{showMessage();}catch(ex){}">
假设showMessage()函数是在按钮下方,页面的最底部定义的。如果用户在页面解析showMessage()
之前就单机了按钮,就会引发错误。为此,很多HTML事件处理程序都会被装在一个try-catch块中。
2.2 elem.onclick
DOM0级事件绑定,使用elem.onclick=function(){}
绑定事件,被认为是元素的方法。这时候,事件处理程序是在元素的作用域中进行。1
2
3
4
5var btn = document.getElementById('myBtn');
btn.onclick = function(){
alert(this.id); // 'myBtn',this引用当前元素
}
btn.onclick = null; // 删除事件处理程序
2.3 addEventListener
DOM2级事件绑定: addEventListener() 和 removeEventListener()。接受3个参数:要处理的事件名,事件处理函数,和一个布尔值(true: 在捕获阶段调用事件, false: 在冒泡阶段调用)。为同一个元素添加两次addEventListener,两个事件会按添加顺序依次触发。1
2
3
4
5var handler = function(){
alert(this.id);
}
btn.addEventListener('click', handler, false)
btn.removeEventListener('click', handler, false)
2.4 attachEvent
IE实现了两个类似的方法: attachEvent() 和 detachEvent()。与addEventListener不同,attachEvent,事件处理程序会在全局作用域中运行。1
2
3
4
5var handle = function(){
alert(this === window); // true
}
btn.attachEvent('onclick', handler);
btn.detachEvent('onclick', handler);
3. event对象
在触发dom上的某个事件时,会产生一个事件对象event || window.event(IE)
,这个对象中包含着所有与事件有关的信息。包含导致事件的元素、事件的类型以及其他与特定事件相关的信息。如,鼠标操作导致的事件中,会包含鼠标位置的信息。键盘操作的事件中,会包含与按下的键有关的信息。
属性/方法 | 值 | 说明 |
---|---|---|
bubbles | Boolean | 事件是否冒泡 |
cancelable | Boolean | 是否可以取消事件的默认行为 |
currentTarget | Element | 当前正在处理事件的那个元素 |
defaultPrevented | Boolean | true,表示已经调用了preventDefautl() |
detail | Interger | 与事件相关的细节信息 |
eventPhase | Interger | 调用事件处理程序的阶段:1-捕获阶段,2-处于目标,3-冒泡阶段 |
preventDefault() | Function | 取消事件的默认行为 |
stopImmediatePropagation() | Function | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用 |
stopPropagation() | Function | 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 |
target | Element | 事件的目标 |
trusted | Boolean | 表明事件是浏览器生成的 |
type | String | 被触发的事件的类型 |
view | AbstractView | 与事件关联的抽象视图。等同于发生事件的window对象 |
3.1 currentTarget, target
currentTarget
始终等于对象this的值,target
则只包含事件的实际目标。1
2
3
4
5document.body.onclick = function(event){
alert(event.currentTarget === document.body); // true
alert(this === document.body); // true
alert(event.target === document.getElementById("myBtn")) // true
}
3.2 event.type
在需要通过一个函数处理多个事件时,可以使用type
属性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var handler = function(event){
switch(event.type){
case "click":
alert("clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
}
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
3.3 event.preventDefault()
阻止特定事件的默认行为,例如<a>
被单击时会导航到其href特性指定的URL。1
2
3
4// 取消了链接导航这一默认行为
link.onclick = function(event){
event.prevenDefault();
}
3.4 stopPropagation()
立即停止事件在DOM层次中的传播,取消进一步的事件捕获或冒泡。1
2
3
4
5
6
7
8// 在按钮上调用stopPropagation(),从而避免触发注册在document.body上面的事件
btn.onclick = function(event){
alert("Clicked");
event.stopPropagation();
}
document.body.onclick = function(event){
alert("Body clicked");
}
3.5 IE中的event
属性/方法 | 值 | 说明 |
---|---|---|
cancelBubble | Boolean | 默认为false,设置为true可以取消事件冒泡 |
returnValue | Boolean | 默认为true,设置为false可以取消事件的默认行为 |
srcElement | Element | 事件的目标(与DOM的target属性相同) |
type | String | 事件的类型 |
1 | // function的作用域是根据指定它的方式来确定的,不能认为this始终等于事件目标。用event.srcElement比较保险 |
3.6 event.clientX, pageX,screenX
得知事件发生的坐标。1
2
3
4
5
6
7
8
9
10btn.onclick = function(event){
// 浏览器视口中的特定位置上发生
alert('Client coordinates:' + event.clientX + event.clientY);
// 在页面中的位置,页面没有滚动的情况下,pageX和clientX相等
alert('Page coordinates:' + event.pageX + event.pageY);
// 相对于整个电脑屏幕的位置
alert('Screen coordinates:' + event.screenX + event.screenY);
}
3.7 修改键
在按下鼠标键盘上的某些键的状态也可以影响到所要采取的操作。这些修改键是shift, ctrl, alt和meta(windows里是windows键,苹果梨是cmd键)。DOM定义了4个属性,来表示这些修改键的状态: shiftKey, ctrlKey, altKey, metaKey(Boolean值)1
2
3btn.onclick = function(event){
if (event.shiftKey){}
}
4. 跨浏览器绑定事件
1 | var EventUtil = { |
5. 事件类型
5.1 load
window.load
当页面完全加载完后(包括所有图像,js文件,css文件等外部资源),就会触发window上的load事件。1
2
3
4EventUtil.addHandler(window, "load", function(event){
alert("Loaded");
})
<body onload="alert('Loaded')"></body>
图像上也可以触发load事件,图像加载完毕后触发事件。1
2
3
4EventUtil.addHandler(imgElem, "load", function(event){
alert("image loaded");
})
<img src="1.jpg" onload="alert('image loaded')">
<script>
元素也会触发load事件,以便确定动态加载的js文件是否加载完毕。1
2
3
4var script = document.createElement("script");
script.onload = function(){};
script.src = "1.js";
document.body.appendChild(script);
5.2 焦点事件
焦点事件会在页面元素获得或失去焦点时触发。利用这些事件与document.hasFocus()
及document.activeElement
属性配合,可以知晓用户在页面上的行踪。
- document.hasFocus()
- document.activeElement
- focus: 在元素获得焦点时触发
- blur: 在元素失去焦点时触发
5.3 鼠标与滚轮事件
鼠标事件 | 说明 |
---|---|
click | 单击。只有在同一个元素上相继触发mousedown和mouseup事件,才会触发click事件 |
dbclick | 双击 |
mousedown | 用户按下任意鼠标按钮时触发 |
mouseup | 用户释放鼠标按钮时触发 |
mouseenter | 光标从元素外部首次移动到元素范围内触发 |
mouseleave | 在位于元素上方的光标移动到元素范围外触发 |
mousemove | 当光标在元素内部移动时重复触发 |
mouseover | 鼠标进入,同mouseover |
mouseout | 鼠标移出, 同mouseout |
事件触发顺序: mousedown -> mouseup -> click
5.4 键盘事件
键盘事件 | 说明 |
---|---|
keydown | 当用户按下键盘上的任意键时触发,长按不放,会重复触发 |
keypress | 当用户按下键盘上的字符键时触发,长按不放,会重复触发 |
keyup | 当用户释放键盘上的键时触发 |
用户按下一个字符键时候,会依次触发 keydown -> keypress -> keyup。
- event.keycCode 非字符键的键码
在发生keydown和keyup事件时,会有一个event.keyCode,与键盘上一个特定的键对应。
- event.charCode 字符键的键码
只有在keypress的时候才触发,event.charCode
是键的ascii码。
event.key, event.keyIdentifier, event.keyLocation
DOM3级事件的键盘事件,不再包含charCode属性,而包含了两个新属性: key和char。event.key
对应的是相应的文本字符(如k,M)。event.keyIdentifier
返回一个类似”U+0000”的字符串,表示Unicode值。(chrome和safari支持)event.location || event.keyLocation
表示按下了什么位置上的键: 0-默认键盘,1-左侧位置, 2-右侧位置, 3-数字小键盘, 4-移动设备键盘(虚拟键盘), 5-手柄。(IE9,safari,chrome支持)textInput事件
DOM3引入了一个新事件,只有可编辑区域才能触发textInput事件。它的event.data
属性,就是用户输入的字符。event.inputMethod
,表示文本输入到文本框中的方式。
5.5 html5事件
5.5.1 contextmenu
设置鼠标右击菜单1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17EventUtil.addHandler(window, 'load', function(event){
var div = document.getElementById('myDiv');
EventUtil.addHandler(div, 'contextmenu', function(event){
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
var menu = document.getElementById('myMenu');
menu.style.left = event.clientX + 'px';
menu.style.top = event.clientY + 'px';
menu.style.visibility = 'visible';
});
EventUtil.addHandler(document, 'click', function(event){
document.getElementById('myMenu').style.visibility = 'hidden';
})
})
5.5.2 beforeunload
IE, firefox, safari, chrome都支持beforeunload
事件,在浏览器卸载页面之前询问用户是否真的要关闭页面。1
2
3
4
5EventUtil.addHandler(window, 'beforeunload', function(event){
event = EventUtil.getEvent(event);
event.returnValue = "i'm really going to miss you if you go.";
return message;
})
5.5.3 DOMContentLoaded
window的load事件会在页面中的一切都加载完毕时触发,DOMContentLoaded事件则会在形成完整的DOM树之后触发。不理会图像,js,css或其他资源是否已经下载完毕。
5.5.4 pageshow, pagehide
window.pageshow
页面显示时触发。 window.pagehide
在浏览器卸载页面时触发,在unload事情前触发。
5.5.5 hashchange
在ajax应用中,开发人员经常要利用url参数来保存状态或导航信息。url中#号后面的字符串发生变化时, hashchange事件就会被触发。1
2
3EventUtil.addHandler(window, 'hashchange', function(event){
console.log('current hash:' + location.hash);
})
5.6 设备事件
5.6. 1 orientationchange
苹果safari添加了orientationchange时间,确定用户何时将设备由横向查看模式切换为纵向查看模式。1
2
3
4EventUtil.addHanlder(window, 'load', function(event){
// 0 肖像模式,90 左旋转的横向模式, -90 右旋转的横向模式
div.innerHTML = 'Current orientation is' + window.orientation;
})
5.6.2 deviceorientation
表示设备在空间中朝向哪里。1
2
3
4EventUtil.addHandler(window, 'deviceorientation', function(event){
// alpha, beta, gamma分别指示围绕z,x,y轴旋转时y轴,z轴,z轴的度数差
console.log(event.alpha + event.beta + event.gamma);
})
5.6.3 devicemotion
表示设备什么时候移动。事件对象包含以下属性: acceleration每个方向上的加速度; accelerationIncludingGravity在考虑z轴自然重力加速度的情况下,告诉你在每个方向上的加速度。rotationRate方向值。
5.7 触摸与手势事件
touchstart : 当手指触摸屏幕时触发
touchmove : 当手指在屏幕上滑动时连续第触发
touchend : 当手指在屏幕上移开时触发
touchcancel : 当系统停止跟踪触摸时触发
只有两个手指都触摸,才会触发这些事件
gesturestart : 当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发
gesturechange : 当触摸屏幕的任何一个手指的位置发生变化时触发
gestureend : 当任何一个手指从屏幕上面移开时触发
其他
unload
window.onunload
在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件。这个事件最多的情况是清除引用,以避免内存泄露。resize
window.onresize
浏览器窗口被调整到一个新的高度或宽度时触发scroll
window.scroll
在文档被滚动期间重复触发DOM变动事件
DOMNodeRemoved
会在使用removeChild()或replaceChild()删除结点时触发,event.target是被删除的节点,event.relatedNode包含着目标节点父节点的引用。DOMNodeInserted
会在appendChild(), replaceChild(), insertBefore()插入节点时触发。
6. 内存与性能
6.1 事件委托
对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,为整个页面指定一个onclick事件处理程序,而不必为每个可单击的元素分别添加事件处理程序。1
2
3
4
5
6
7
8
9
10EventUtil.addHandler(list, 'click', function(event){
var event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id){
case 'doSomething':
doSomething();
break;
}
})
document对象很快就可以访问,可以在页面生命周期的任何时点上为它添加事件处理程序(无需等待DOMContentLoaded或load事件)。只要可单击的元素呈现在页面上,就可以立即具备适当的工鞥。只添加一个事件处理程序所需的DOM引用更少,占用的内存空间更少,所花的时间也更少。
6.2 模拟事件
createEvent
可以创建事件,通过dispatchEvent
触发事件。
UIEvent 鼠标事件和键盘事件, MouseEvents 鼠标事件, MutationEvents DOM变动事件,HTMLEvents HTML事件。1
2
3
4
5
6
7
8
9
10
11// 触发按钮点击事件
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);
参考资料
- 《Javascript高级程序设计》第13章 事件