事件是什么,可以用来做什么,什么时候用到它?
事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。JavaScript与HTML之间的交互是通过事件实现的
事件流
事件流描述的是从页面中接收事件的顺序
事件冒泡(event bubbling)
事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)
事件捕获(event capturing)
document对象首先接收到事件,然后事件沿DOM树依次向下,一直传播到事件的实际目标
DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段
在DOM事件流中,实际的目标在捕获阶段不会接收到事件,目标的事件处理被看成冒泡阶段的一部分。但IE9、Safari、Chrome、FireFox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作事件。
事件处理程序
响应某个事件的函数叫做事件处理程序(或事件侦听器)
HTML事件处理程序
某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行JavaScript代码
<input type="button" value="Click Me" onclick="alert(this.value)">
或
<script type="text/javascript">
function showMessage() {
alert("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()">
this值等于事件的目标元素
一般很少使用HTML事件处理程序,原因是它有如下几个缺点:
- 有调用顺序的要求,事件函数必须在HTML之前定义好
- 扩展事件处理程序的作用域在不同浏览器中会导致不同结果
- HTML和JavaScript代码紧密耦合,如果要更换事件处理程序,就要改动两个地方
DOM0级事件处理程序
var btn = document.getElementById("myBtn");
//绑定事件
btn.onclick = function() {
alert(this.id); //"myBtn"
};
//解绑事件
btn.onclick = null;
作用域:依附的元素的作用域
DOM2级事件处理程序
“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener() 和 removeEventListener()
所有节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值(true表示在捕获阶段调用事件处理程序/false表示在冒泡阶段调用事件处理程序)
使用DOM2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序,依次顺序触发
作用域:依附的元素的作用域
IE事件处理程序
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent(),由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段
同addEventListener(),attachEvent()方法也可以用来为一个元素添加多个事件处理程序,不同的是以相反的顺序被触发
作用域:全局作用域
跨浏览器的事件处理程序
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.removeHandler) {
element.removeHandler(type, handler, false);
} else if(element.detachEvent) {
element.detachEcent("on" + type, handler);
} else {
element["on" + type] =null;
}
}
};
事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。
DOM中的事件对象
无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event对象,event对象包含与创建它的特定事件有关的属性和方法,触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下列属性和方法:
bubbles
cancelable
currentTarget
defaultPrevented
detail
eventPhase
preventDefault()
stopImmediatePropagation()
stopPropagation()
target
trusted
type
view
具体含义看这里
在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标
IE中的事件对象
DOM0级:event对象作为window对象的一个属性存在
attachEvent():event对象作为参数被传入事件处理函数中,也可以通过window对象来访问event对象
IE的event对象同样也包含与创建它的事件相关的属性和方法。其中很多属性和方法都有相应的或者相关的DOM属性和方法,但所有事件对象都会包含下列属性和方法:
cancelBubble
returnValue
srcElement
type
跨浏览器的事件对象
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;
}
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function() {
if(event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler) {
if(element.removeHandler) {
element.removeHandler(type, handler, false);
} else if(element.detachEcent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] =null
}
},
stopPropagation: function(event) {
if(event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
事件类型
“DOM3级事件”规定了以下几类事件:
- UI(User Interface,用户界面)事件,当用户和页面上的元素交互时触发;
- 焦点事件,当元素获取或失去焦点时触发;
- 鼠标事件,当用户通过鼠标在页面上执行操作时触发;
- 滚轮事件,当使用鼠标滚轮(或类似设备)时触发;
- 文本时间,当在文档中输入文本时触发;
- 键盘事件,当用户通过键盘在页面上执行操作时触发;
- 合成事件,当为IME(Input Method Editor,输入法编辑器)输入字符时触发;
- 变动(mutation)事件,当底层DOM结构发生变化时触发;
- 变动名称事件,当元素或属性名变动时触发。(已废弃)
UI事件
DOMActive
(DOM3已废弃该事件)load
unload
abort
error
select
resize
scroll
可以使用document.implementation.hasFeature("HTMLEvents", "2.0");
来确定浏览器是否支持“DOM2级事件”;使用document.implementation.hasFeature("UIEvent", "3.0");
来确定是否支持“DOM3级事件”
-
load
事件
当页面完全加载后(包括所有图像、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件;
<img>上面也可以触发load事件,无论是在DOM中的图像元素还是HTML中的图像元素。注意,新图像元素不一定要从添加到文档后才开始下载,只要设置了src属性就会开始下载;
<script>元素也会触发load事件,以便开发人员确定动态加载的JavaScript文件是否加载完毕。与图像不同,只有在设置了<script>元素的src属性并将该元素添加到文档中,才会开始下载JavaScript文件。
IE和Opera还支持<link>元素上的load事件,以便开发人员确定样式表是否加载完毕(与<script>节点类似,在未指定href属性并将<link>元素添加到文档之前也不会开始下载样式表)。
根据“DOM2级事件”规范,应该在document而非window上面触发load事件。但是,所有浏览器都在window上面实现了该事件,以确保向后兼容。
-
unload
事件
与load事件相对,这个事件在文档被完全卸载后触发。而利用这个事件最多的情况是清除引用,以避免内存泄漏。
既然unload事件是在一切都被卸载之后才触发,那么在页面加载后存在的那些对象,此时就不一定存在了。此时,操作DOM节点或者元素的样式就会导致错误
根据“DOM2级事件”,应该在<body>元素而非window对象上面触发unload事件。不过,所有浏览器都在window上实现了unload事件,以确保向后兼容。
-
resize
事件
当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件。
关于何时会触发resize事件,不同浏览器有不同的机制。IE、Safari、Chrome和Opera会在浏览器窗口变化了1像素时就触发resize事件,然后随着变化不断重复触发。Firefox则只会在用户停止调整窗口大小时才会触发resize事件。由于存在这个差别,应该注意不要在这个事件的处理程序中加入大计算量的代码,因为这些代码有可能被频繁的执行,从而导致浏览器反应明显变慢。
浏览器窗口最小化或最大化时也会触发resize事件 -
scroll
事件
虽然scroll事件是在window对象上发生的,但它实际表示的则是页面中相应元素的变化。在混杂模式下,可以通过<body>元素的scrollLeft和scrollTop来监控到这一变化;而在标准模式下,除Safari之外的所有浏览器都会通过<html>元素来反映这一变化(Safari仍然基于<body>跟踪滚动位置)。
EventUtil.addHandler(window, "scroll", function(event) {
if(document.compatMode === "CSS1Compat") {
alert(document.documentElement.scrollTop);
} else {
alert(document.body.scrollTop);
}
});
焦点事件
blur
(该事件不会冒泡)DOMFocusIn
(DOM3已废弃) DOMFocusOut
(DOM3已放弃) focus
(该事件不会冒泡) focusIn
focusOut
当焦点从页面中的一个元素移动到另一个元素,会依次触发下列事件:
- focusOut在失去焦点的元素上触发;
- focusIn在获得焦点的元素上触发;
- blur在失去焦点的元素上触发;
- DOMFocusOut在失去焦点的元素上触发;
- focus在获得焦点的元素上触发;
- DOMFocusIn在获得焦点的元素上触发。
要确定浏览器是否支持这些事件,可以使用document.implementation.hasFeature("FocusEvent", "3.0")
即使focus和blur不冒泡,也可以在捕获阶段侦听到它们
鼠标与滚轮事件
鼠标事件
click
dblclick
(DOM3新增) mousedown
mouseenter
(不冒泡,DOM3新增) mouseleave
(不冒泡,DOM3新增) mousemove
mouseout
mouseover
mouseup
只有在同一元素上相继触发mousedown和mouseup事件,才会触发click事件,如果mousedown和mouseup中的一个被取消,就不会触发click事件
只有触发两次click事件才会触发一次dblclick事件,如果有代码阻止了连续两次触发click事件(可能是直接取消click事件,也可能通过取消mousedown或mouseup间接实现),那么就不会触发dblclick事件了
这4个事件触发的顺序是:
- mousedown
- mouseup
- click
- mousedown
- mouseup
- click
- dblclick
要监测浏览器是否支持以上DOM2级事件,可以使用document.implementation.hasFeature("MouseEvents", "2.0");
,监测是否支持DOM3级事件,可以使用document.implementation.hasFeature("MouseEvent", "3.0");
滚轮事件
滚轮事件只有一个mousewheel
- 客户区坐标位置
鼠标事件都是在浏览器视口中的特定位置上发生的,这个位置信息保存在事件对象的clientX
clientY
注意这个值不包括页面滚动的距离,因此这个位置并不代表鼠标在页面上的位置 - 页面坐标位置
页面坐标通过事件对象的pageX和pageY属性,能告诉你事件是在页面中的什么位置发生的,也就是表示鼠标光标在页面中的位置。
IE8及更早版本不支持事件对象上的页面坐标,不过使用客户区坐标和滚动信息(document.documentElement.srollLeft[scrollTop] || document.body.scrollLeft[scrollTop]
)可以计算出来 - 屏幕坐标位置
通过screenX和screenY属性就可以确定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息 - 修改键
DOM规定了4个属性,shiftKey、ctrlKey、altKey和metaKey(Windows键盘中的Windows键,Mac上是Cmd键),这些属性都是布尔值。如果相应的键被按下了,则值为true,否则值为false。
IE8及之前版本不支持metaKey属性 - 相关元素
DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseover
和mouseout
事件才包含值;对于其他事件,这个属性的值是null
IE8及之前版本不支持relatedTarget属性,但提供了保存着同样信息的不同属性。在mouseover事件触发时,IE的fromElement属性中保存了相关元素;在mouseout事件触发时,IE的toElement属性中保存着相关元素 - 鼠标事件
只有在主鼠标按钮被点击(或键盘回车键被按下)时才会触发click事件,但对于mousedown和mouseup事件来说,则在其event对象存在一个button属性,表示按下或释放的按钮,有3个值:0表示主鼠标按钮,1表示中间的鼠标按钮(鼠标滚轮按钮),2表示次鼠标按钮
IE8及之前版本也提供了button属性,但这个属性的值与DOM的button属性有很大差异,有多达8种组合 - 更多的事件信息
“DOM2级事件”规范在event对象中还提供了detail属性,用于给出有关事件的更多信息
对于鼠标事件来说,detail中包含了一个数值,表示在给定位置上发生了多少次单击。在同一像素上相继地发生一次mousedown和一次mouseup事件算一次单击。detail属性从1开始计数,每次单击后都会递增。如果鼠标在mousedown和mouseup之间移动了位置,则detail会被重置为0
IE通过额外的5个属性为鼠标事件提供了更多信息 - 鼠标滚轮事件
mousewheel事件可以在任何元素上面触发,最终会冒泡到document(IE8)或window(IE9、Opera、Chrome及Safari)对象
与mousewheel事件对应的event对象除包含鼠标事件的所有标准信息外,还包含一个特殊的wheelDelta属性(向前滚动鼠标滚轮时,wheelDelta是120的倍数;向后滚动鼠标滚轮时,是-120的倍数)
Opera9.5之前的版本中,wheelDelta值的正负号是颠倒的
Firefox支持一个名为DOMMouseScroll的类似事件,也就是鼠标滚轮滚动时触发,并包含与鼠标事件有关的所有属性,而有关鼠标滚轮的信息保存在detail属性中,向前滚动鼠标滚轮时,这个属性的值是-3的倍数,向后滚动鼠标滚轮时,是3的倍数 - 触摸设备
iOS和Android设备没有鼠标,所以在面向iPhone和iPod中的Safari开发时,要记住以下几点:
- 不支持dblclick事件。双击浏览器窗口会放大画面,而且没有办法改变该行为
- 轻击可点击元素(如链接或者已经被指定了onclick事件处理程序的元素)会触发mousemove事件。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有变化,那么会依次发生mousedown、mouseup和click事件
- mouseover事件也会触发mouseover和mouseout事件
- 两个手指放在屏幕上且页面随手指移动并滚动时会触发mousewheel和scroll事件
- 无障碍性问题
如果你的Web应用程序或网站要确保残疾人特别是那些使用屏幕阅读器的人都能访问,那么在使用鼠标事件时要格外小心。由于除click事件外的鼠标事件无法通过键盘来触发,不建议使用click之外的其他鼠标事件来展示功能或引发代码执行
键盘与文本事件
- keydown:按下键盘上的任意键时触发,如果按住不放的话,会重复触发此事件
- keypress:按下键盘上的字符键时触发,如果按住不放的话,会重复触发此事件
- keyup:释放键盘上的键时触发
- textInput:文本插入文本框之前会触发此事件
- 键码
在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母键,keyCode属性的值与ASCII码中对应小写字母或数字的编码相同,与Shift键的状态无关。DOM和IE的event对象都支持keyCode属性
有一些特殊情况,如在Firefox和Opera中,按分号键时keyCode值为59,也就是ASCII中分号的编码;但IE和Safari返回186,即键盘中按键的键码 - 字符编码
发生keypress事件意味着按下的键会影响到屏幕文本的显示(包括插入和删除)。IE9、Firefox、Chrome和Safari的event对象都支持一个charCode属性,这个属性只有在发生keypress事件时才包含值(该键所代表的ASCII编码),此时的keyCode通常为0或者也可能等于所按键的键码(IE8及之前版本和Opera的keyCode中存的是ASCII编码) - DOM3变化
- DOM3级事件中的键盘事件不再包含charCode属性,而是包含两个新属性:key(字符键:相应文本字符,非字符键:键名)和char(字符键:相应文本字符,非字符键:null);
- 添加了location属性(表示按下了什么位置的键);
- 为event对象添加了getModifierState(要检测的修改键)方法,如果指定的修改键处于被按下的状态,这个方法返回true,否则返回false
由于存在跨浏览器的问题,不推荐使用这些特性
- textInput事件
“DOM3级事件”规范中引入了一个新事件,名叫textInput
该事件与keypress主要有如下几个区别:
- 前者只有在可编辑区域才能触发,而后者在任何可以获得焦点的元素上都可以触发
- 前者只会在按下能够输入实际字符的键时才会出发,而后者在按下那些能够影响文本显示的键时也会触发(例如:退格键)
textInput事件的event对象中还包含一个data属性,表示输入的字符(而非字符编码),IE9+、Safari和Chrome均支持;还包含一个inputMethod属性,表示把文本输入到文本框中的方式,只有IE支持该属性
- 设备中的键盘事件
任天堂Wii会在用户按下遥控器上的按键时触发键盘事件。
iOS版Safari和Android版WebKit在使用屏幕键盘是会触发键盘事件。
复合事件
复合事件是DOM3级事件中新添加的一类事件,用于处理IME(输入法编辑器,可以让用户输入在物理键盘上找不到的字符)的输入序列。IME通常需要同时按住多个键,但最终只输入一个字符。复合事件就是针对这种检测和处理这种输入而设计的。
复合事件有三种:compositionstart
compositionupdate
compositionend
另外,它比文本事件的事件对象多一个属性data
IE9+是唯一支持该事件的浏览器,要确定浏览器是否支持复合事件,可以使用document.implementation.hasFeature("CompositionEvent", "3.0");
变动事件
DOM2级的变动(mutation)事件能在DOM中的某一部分发生变化时给出提示,DOM2级定义了如下变动事件:
- DOMSubtreeModified:在DOM结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发;
- DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发;
- DOMNodeRemoved:在节点从其父节点中被移除时触发;
- DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在DOMNodeInserted之后触发;
- DOMNodeRemovedFromDocument:在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在DOMNodeRemoved之后触发;
- DOMAttrModified:在特性被修改之后触发;
- DOMCharacterDataModified:在文本节点的值发生变化时触发
通过document.implementation.hasFeature("MutationEvents", "2.0");
检测浏览器是否支持变动事件
IE8及更早版本不支持任何变动事件
- 删除节点
删除一个节点变化事件执行顺序
- 被删除元素上触发DOMNodeRemoved事件,relatedNode属性为其父节点;
- 被删除元素上触发DOMNodeRemovedFromDocument事件;
- 被删除元素的每个子节点上触发DOMNodeRemovedFromDocument事件;
- 在被删除元素的父节点上触发DOMSubtreeModified事件
- 插入节点
插入一个节点变化事件执行顺序
- 被插入元素上触发DOMNodeInserted事件,relatedNode属性为其父节点;
- 被插入元素上触发DOMNodeInsertedIntoDocument事件;
- 在被插入元素的父节点上触发DOMSubtreeModified事件
HTML5事件
- contextmenu事件
表示何时应该显示上下文菜单,以便取消默认的上下文菜单从而提供自定义的菜单,下例的代码实现了自定义的上下文菜单:
<div id="myDiv">右键</div>
<ul id="myMenu" style="position:absolute;visibility:hidden;background-color:silver;">
<li><a href="http://www.site1.com">site1</a></li>
<li><a href="http://www.site2.com">site2</a></li>
<li><a href="http://www.site3.com">site3</a></li>
</ul>
EventUtil.addHandler(window, "load", function(event) {
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "contextmenu", function() {
EventUtil.preventDefault(); // 保证不显示浏览器默认的上下文菜单
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";
});
});
支持contextmenu事件的浏览器有IE、Firefox、Safari、Chrome和Opera 11+
- beforeunload事件
这个事件会在浏览器卸载页面之前触发,可以通过它来取消卸载并继续留在原有页面
EventUtil.addHandler(window, "beforeunload", function(event) {
event = EventUtil.getEvent(event);
var message = "I'm really going to miss you if you go.";
event.returnValue = message; // 对于IE和Firefox而言,必须将event.returnValue的值设置为要显示给用户的字符串
return message; // 对于Safari和Chrome而言,必须将要显示给用户的字符串作为函数的值返回
});
Opera 11及之前的版本不支持beforeunload事件
- DOMContentLoaded事件
window的load事件会在页面中的一切都加载完毕时触发,而DOMContentLoaded事件则在形成完整的DOM树之后就会触发,不理会图像、JavaScript文件、CSS文件或其他资源是否已经下载完毕。与load事件不同,DOMContentLoaded支持在页面下载的早期添加事件处理程序,这也就意味着用户能够尽早地与页面进行交互。
DOMContentLoaded事件对象不会提供任何额外的信息(其target属性是document)
对于不支持DOMContentLoaded的浏览器,建议在页面加载期间设置一个时间为0毫秒的超时调用(在当前JS处理完成后立即运行这个函数),为了确保尽可能与DONContentLoaded被触发的时间尽可能同步,必须将其作为页面中的第一个超时调用(即便如此,也还是无法保证在所有环境中该超时调用一定会早于load事件被触发)。 - readystatechange事件
IE为DOM文档中的某些部分提供了readystatechange事件,为了提供与文档或元素加载有关的信息,但这个事件的行为有时候也很难预料。支持readystatechange事件的每个对象都有一个readyState属性,可能包含下列5个值中的一个:uninitialized
loading
loaded
interactive
complete
这个事件的event对象不会提供任何信息,也没有目标对象
对于document而言,值为"interactive"的readyState会在与DOMContentLoaded大致相同的时刻触发readystatechange事件;在与load事件一起使用时,无法预测两个事件触发的先后顺序
支持readystatechange事件的浏览器有IE、Firefox 4+和Opera
<script>(在IE和Opera中)和<link>(仅IE中)元素也会触发readystatechange事件,可以用来确定外部的JavaScript和CSS文件是否已经加载完成,下面展示了一段加载外部JavaScript文件的代码
EventUtil.addHandler(window, "load", function() {
var script = document.createElement("script");
EventUtil.addHandler(script, "readystatechange", function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
// 如果进入loaded或complete任何一个阶段,都可以表示资源可用,则移除事件处理函数(以防止被执行两次)
if(target.readyState == "loaded" || target.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("Script Loaded");
}
});
script.src = "example.js";
document.body.appendChild(script);
});
最重要的是要一并检测readyState的两个状态,并在调用了一次事件处理程序后就将其移除
- pageshow和pagehide事件
Firefox和Opera有一个特性,名叫“往返缓存”(back-forward cache,或bfcache),可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。如果页面位于bfcache中,那么再次打开该页面时就不会触发load事件,但为了更形象地说明bfcache的行为,Firefox还是提供了一些新事件
第一个事件就是pageshow,这个事件在页面显示时触发,无论是否来自bfcache。来自非bfcache的页面会在load事件触发后触发;而对于来自bfcache的页面,会在页面状态完全恢复的那一刻触发
虽然这个事件的目标是document,但必须将其事件处理程序添加到window
除了通常的属性之外,pageshow事件的event对象还包含一个名为persisted布尔值属性,标识页面是否被保存在了bfcache中
与pageshow事件对应的是pagehide事件,该事件会在浏览器卸载页面的时候触发,而且是在unload事件之前触发。这个事件的特性与pageshow相同,不同点是persisted属性的用途,标识页面卸载之后是否被保存在bfcache中
Firefox、Safari 5+、Chrome和Opera均支持这两个事件,IE9及之前版本不支持这两个事件 - hashchange事件
HTML5新增haschange事件,标识URL的参数列表是否发生变化
必须要在hashchange事件处理程序添加给window对象,然后URL参数列表只要变化就会调用它,此时的event对象应该额外包含两个属性:oldURL和newURL,这两个属性分别保存着参数列表变化前后的完整URL
支持该事件的浏览器有IE8+、Firefox 3.6+、Safari 5+、Chrome和Opera 10.6+,在这些浏览器中,只有Firefox 6+、Chrome和Opera支持oldURL和newURL属性,为此,最好使用location对象来确定当前的参数列表
可以使用("onhashchange" in window) && (document.documentMode === undefined || document.documentMode > 7)
(如果IE8是在IE7文档模式下运行,即使功能失效也会返回true)来确定是否支持该事件
设备事件
- orientationchange事件
苹果公司为移动Safari中添加了该事件,以便确定设备何时由横向查看模式切换为纵向查看模式
window.orientation属性中可能包含3个值:
0 表示肖像模式
90 表示向左旋转的横向模式(home键在右)
-90 表示向右旋转的横向模式(home键在左)
180 表示iPhone头朝下(这种模式至今尚未得到支持)
所有iOS设备都支持orientationchange事件和window.orientation - MozOrientation事件
Firefox 3.6为检测设备的方向引入了一个名为MozOrientation的新事件,当设备的加速计检测到设备方向改变时,就会触发这个事件,该事件在window对象上触发
该事件对象包含3属性:x、y和z
这几个属性的值都介于1到-1之间,表示不同坐标轴上的方向
在静止状态下,x值为0,y值为0,z值为1(表示设备处于竖直状态)
如果设备向右倾斜,x值会减小;反之,向左倾斜,x值会增大
类似地,如果设备向远离用户的方向倾斜,y值会减小,向接近用户的方向倾斜,y值会增大
z轴检测垂直加速度,1表示静止不动,在设备移动时值会减小(失重状态下为0)
只有带加速计的设备才支持该事件 - deviceorientation事件
与MozOrientation事件类似,它也是在加速计检测到设备方向变化时在window对象上触发,而且具有与MozOrientation事件相同的支持限制,不过,该事件主要意图是告诉开发人员设备在空间中朝向哪儿,而不是如何移动
设备在三维空间中是靠x、y和z轴来定位的。当设备静止放在水平表面上时,这三个值都是0。x轴方向是从左往右,y轴方向是从下往上,z轴方向是从后往前,参见下图:
该事件触发时,事件对象包含5个属性:alpha
beta
gamma
absolute
compassCalibrated
支持该事件的浏览器有iOS 4.2+中的Safari、Chrome和Android版WebKit - devicemotion事件
该事件是要告诉开发人员设备什么时候移动,而不仅仅是设备方向如何改变。例如,通过该属性能够检测到设备是不是正在往下掉,或者是不是被走着的人拿在手里。
触发该事件时,事件对象包括以下属性:acceleration
accelerationIncludingGravity
interval
rotationRate
支持该事件的浏览器有iOS 4.2+中的Safari、Chrome和Android版WebKit
触摸与手势事件
- 触摸事件
- touchstart:当手指触摸屏幕时触发;即使已经有一个手纸放在了屏幕上也会触发;
- touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault()可以阻止滚动;
- touchend:当手指从屏幕上移开时触发;
- touchcancel:当系统停止跟踪触摸时触发(确切触发时间,文档中没有明确说明)
这几个事件都会冒泡,也可以取消。虽然没有在DOM规范中定义,但它们却是以兼容DOM的方式实现的。因此,每个触摸事件的event对象都提供了在鼠标事件中常见的属性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey和metaKey
除了常见的DOM属性外,触摸事件还包括下列三个用于跟踪触摸的属性: - touches:表示当前跟踪的触摸操作的Touch对象的数组
- targetTouches:特定于事件目标的Touch对象的数组
- changeTouches:表示自上次触摸以来发生了什么改变的Touch对象的数组
每个Touch对象包含了下列属性: - clientX:触摸目标在视口中的x坐标;
- clientY:触摸目标在视口中的y坐标;
- identifier:标识触摸的唯一ID;
- pageX:触摸目标在页面中的x坐标;
- pageY:触摸目标在页面中的y坐标;
- screenX:触摸目标在屏幕中的x坐标;
- screenY:触摸目标在屏幕中的y坐标;
- target:触摸的DOM节点目标
这些事件在元素上发生的顺序如下:
(1) touchstart
(2) mouseover
(3) mousemove
(4) mousedown
(5) mouseup
(6) click
(7) touchend
支持触摸事件的浏览器包括iOS版Safari、Android版WebKit、bada版Dolfin、OS6+中的BlackBerry WebKit、Opera Mobile 10.1+和LG专有OS中的Phantom浏览器。目前只有iOS版Safari支持多点触摸。桌面版Firefox 6+和Chrome也支持触摸事件
- 手势事件
当两个手指触摸屏幕时就会产生手势,手势通常会改变显示项的大小,或者旋转显示项
- gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发;
- gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发;
- gestureend:当任何一个手指从屏幕上面移开时触发
只有两个手指都触摸到事件的接收容器时才会触发这些事件。当一个手指放在屏幕上时,会触发touchstart事件,如果另一个手指又放在了屏幕上,则会先触发gesturestart事件,随后触发基于该手指的touchstart事件。如果一个或两个手指在屏幕上滑动,将会触发gesturechange事件。但只要有一个手指移开,就会触发gestureend事件,紧接着又会触发基于该手指的touchend事件
与触摸事件一样,每个手势事件的event对象都包含着标准的鼠标事件属性,此外还包含两个额外的属性:rotation和scale,前者表示手指变化引起的旋转角度,负值表示逆时针旋转,正值表示顺时针旋转(该值从0开始);后者表示两个手指间距离的变化情况(例如向内收缩会缩短距离);这个值从1开始,并随距离拉大而增大,随距离缩短而减小
内存和性能
事件使用不当会导致很多性能问题,
首先,每个函数都是对象,都会占用内存;内存中对象越多,性能就越差;
其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
从如何利用好事件处理程序的角度出发,还是有一些方法能够提升性能的
事件委托
对“事件处理程序过多”问题的解决方案就是事件委托
事件委托利用冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
如果可行的话,可以考虑为document对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件。这样做与采取传统的做法相比具有如下优先:
- document对象很快就可以访问,而且可以在页面生命周期的任何时间点上为它添加事件处理程序(无需等待DOMContentLoaded或load事件);
- 在页面中设置事件处理程序所需的时间更少。只添加一个事件处理程序所需的DOM引用更少,所花的时间也更少;
- 整个页面占用的内存空间更少,能够提升整个性能
最适合采用事件委托技术的事件包括click、mousedown、mouseup、keydown、keyup和keypress。虽然mouseover和mouseout事件也冒泡,但是适当处理它们并不容易,而且经常需要计算元素的位置
移除事件处理程序
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制连接数量。另外,在不需要的时候移除事件处理函数,也是解决这个问题的一种方案。内存中留有那些过时不用的“空事件处理程序”,也是造成Web应用程序内存与性能问题的主要原因。
在两种情况下,可能会造成上述问题。
第一种情况就是从文档中移除带有事件处理程序的元素时。这可能是通过纯粹的DOM操作(removeChild()和replaceChild()方法),但更多地是发生在使用innerHTML替换页面中的某一部分的时候。最好的做法就是在移除元素之前先移除事件处理程序。
第二种情况是卸载页面的时候。IE8及更早版本在这种情况是问题比较多,其他浏览器或多或少也有类似问题。最好的做法就是在页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。
注意,使用onunload事件处理程序意味着页面不会被缓存在bfcache中
模拟事件
可以使用JavaScript在任意时刻来触发特定的事件,而此时的事件就如同浏览器创建的事件一样,这些事件该冒泡还会冒泡,而且照样能够导致浏览器执行已经指定的处理它们的事件处理程序。为此,DOM2级规范为此规定了模拟特定事件的方式,IE9、Opera、Firefox、Chrome和Safari都支持这种方式。IE有它自己模拟事件的方式。
DOM中的事件模拟
可以在document对象上使用createEvent()方法创建event对象。这个方法接受一个参数,即表示要创建的事件类型的字符串。在DOM2级中,所有这些字符串都使用英文复数形式,而在DOM3级中都变成了单数。这个字符串可以是下列几个字符串之一。
- UIEvents:一般化的UI事件。鼠标事件和键盘事件都继承自UI事件。DOM3级中时UIEvent;
- MouseEvents:一般化的鼠标事件。DOM3级中是MouseEvent;
- MutationEvents:一般化的DOM变动事件。DOM3级中是MutationEvent;
- HTMLEvents:一般化的HTML事件。没有对应的DOM3级事件(HTML事件被分散到其他类别中)。
在创建了event对象之后,还需要使用与事件有关的信息对其进行初始化。每种类型的event对象都有一个特殊的方法,为它传入适当的数据就可以初始化该event对象。
模拟事件的最后一步是就是触发事件。这一步需要使用dispatchEvent()方法,所有支持事件的DOM节点都支持这个方法。调用dispatch方法时,需要传入一个参数,即表示要触发事件的event对象。
- 模拟鼠标事件
创建新的鼠标事件对象并为其指定必要的信息,就可以模拟鼠标事件。创建鼠标事件对象的方法是为createEvent()传入字符串"MouseEvents"。返回的对象有一个名叫initMouseEvent()方法,用于指定与该鼠标事件有关的信息。这个方法接收15个参数,分别与鼠标事件中每个典型的属性一一对应。
var btn = document.querySelector("myBtn");
// 创建事件对象
var event = document.createElement("MouseEvents");
// 初始化事件对象(参数含义 [type][bubbles][cancelable][view][detail][screenX][screenY][clientX][clientY][ctrlKey][altKey][shiftKey][metaKey][button][relatedTarget])
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
// 触发事件
btn.dispatchEvent(event);
- 模拟键盘事件
DOM3级规定,调用createEvent()并传入"KeyboardEvent"就可以创建一个键盘事件。返回的事件对象会包含一个initKeyEvent()方法。
由于DOM3级不提倡使用keypress事件,因此只能利用这种技术来模拟keydown和keyup事件。
var textbox = document.getElementById("myTextbox"),
event;
// 以DOM3级方式创建事件对象
if(document.implementation.hasFeature("KeyboardEvents", "3.0")) {
event = document.createEvent("KeyboardEvent");
// 初始化事件对象(参数含义[type][bubbles][cancelable][view][key][location][modifiers][repeat])
event.initKeyboardEvent("keydown", true, true, document.defaultView, "a", 0, "Shift", 0);
}
// 触发事件
textbox.dispatchEvent(event);
在Firfox中,调用createEvent()并传入"KeyEvents"就可以创建一个键盘事件。返回的事件对象会包含一个initKeyEvent()方法,这个方法接受10个参数(type、bubbles、cancelable、view、ctrlKey、altKey、shiftKey、metaKey、keyCode、charCode)。将创建的event对象传入到dispatchEvent()方法就可以触发键盘事件
// 只适用于Firefox
var textbox = document.getElementById("myTextbox");
// 创建事件对象
var event = document.createEvent("KeyEvents");
// 初始化事件对象
event.initKeyEvent("keypress", true, true, document.defaultView, false, false, false, false, 65, 65);
// 触发事件
textbox.dispatchEvent(event);
在其他浏览器中,则需要创建一个通用的事件,然后再向事件对象中添加键盘事件特定的信息
var textbox = document.getElementById("Events");
// 创建事件对象
var event = document.createEvent("Events");
// 初始化事件对象
event.initEvent(type, bubbles, cancelable);
event.view = document.defaultView;
event.altKey = false;
event.ctrlKey = false;
event.shiftKey = false;
event.metaKey = false;
event.keyCode = 65;
event.charCode = 65;
// 触发事件
textbox.dispatchEvent(event);
- 模拟其他事件
要模拟变动事件,可以使用createEvent("MutationEvents")创建一个包含initMutationEvent()方法的变动事件对象。这个方法接受的参数包括:type、bubbles、cancelable、relatedNode、preValue、newValue、attrName和attrChange
// 以下代码模拟了DOMInserted事件
var event = document.createEvent("MutationEvents");
event.initMutationEvent("DOMNodeInserted", true, false, someNode, "", "", "", 0);
target.dispatchEvent(event);
- 自定义DOM事件
DOM3级还定义了“自定义事件”。自定义事件不是由DOM触发的,它的目的是让开发人员创建自己的事件。要创建新的自定义事件,可以调用createEvent("CustomEvent")。返回对象有一个名为initCustomEvent()的方法,接收如下4个参数:type、bubbles、cancelable、detail。
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "myevent", function(event) {
alert("DIV:" + event.detail);
});
EventUtil.addHandler(document, "myevent", function(event) {
alert("DOCUMENT:" + event.detail);
});
if(document.implementation.hasFeature("CustomeEvents", "3.0")) {
event = document.createEvent("CustomEvent");
event.initCustomEvent("myevent", true, false, "Hello world!");
div.dispatchEvent(event);
}
支持自定义DOM事件的浏览器有IE9+和Firefox 6+
IE中的事件模拟
在IE8及之前版本中模拟事件与在DOM中模拟事件的思路相似:先创建event对象,然后为其指定相应的信息,然后再使用该对象来触发事件。当然,IE在实现每个步骤时都采用了不一样的方式。
调用document.createEventObject()方法可以在IE中创建event对象。但与DOM方式不同的是,这个方法不接受参数,结果会返回一个通用event对象。然后,你必须手工为这个对象添加必要的信息(没有方法来辅助完成这一步骤)。最后一步就是在目标上调用fireEvent()方法,这个方法接受两个参数:事件处理程序的名称和event对象。在调用fireEvent()方法时,会自动为event对象添加srcElement和type属性;其他属性则都是必须通过手工添加的。换句话说,模拟任何IE支持的事件都采用相同的模式。
var btn = document.getElementById("myBtn");
// 创建事件对象
var event = document.createEventObject();
// 初始化事件对象
event.screenX = 100;
event.screenY = 0;
event.clientX = 0;
event.clientY = 0;
event.ctrlKey = false;
event.altKey = false;
event.shiftKey = false;
event.button = 0;
// 触发事件
btn.fireEvent("onclick", event);
注意:这里可以为对象随意添加属性,不会有任何限制——即使添加的属性IE8及更早版本并不支持页无所谓。在此添加的属性对事件没有什么影响,因为只有事件处理程序才会用到它们
文末附上事件的脑图: