JavaScript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码(属于观察者模式),在DOM元素上注册事件处理程序就是订阅,当事件被触发的时候,DOM元素便会向订阅者发布这个消息。当然我们还可以随意增加或者删除订阅者,增加任何订阅者都不会影响发布者代码的编写。
一、事件流
事件流描述的是从页面中接收事件的顺序,分为事件冒泡和事件捕获,事件冒泡就是targetElement -> body -> document,事件捕获就是document -> body -> targetElement。
由于老版本的浏览器不支持,因此很少有人使用事件捕获。我们也建议读者放心地使用事件冒泡,在有特殊需要时再使用事件捕获。
DOM事件流
DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。document -> body ->targetElement -> body -> document
二、事件处理程序
HTML事件处理程序
某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML属性来指定。属性值可以是一个js语句字符串,例如:alert('hello wrold');也可以是一个定义在全局作用域下的函数调用,在事件被触发时,会执行相关代码。由此可知属性值是指定的函数代码,如果将这个属性值设置为null,也可以移除该事件。
<input type="button" name="clickBtn" value="Echo Username" onclick="show()">
<input type="button" name="clickBtn1" value="test" onclick="alert(this.name)">
<!-- 第二个按钮被点击会弹出"clickBtn1" 在html定义的事件函数的this指向当前元素 -->
<script>
function show() {
console.log(this); // window
// 第一个按钮的oncclick函数定义在js中
// show函数中this指向window(谁调用指向谁)
// show函数是一个全局函数,当第一个input被点击的只是执行了这个show函数
}
</script>
在form中 input的name属性,会在form中生成一个同名指针指向对应的input。在form表单元素中,事件函数的作用域相当于有document、form 、 本身元素,可以像访问局部变量一样访问这些作用域中的属性 ,所以可以直接访问到username.name相当于form.username.value
<form method="post" id="form1">
<input type="text" name="username" value="">
<input type="button" name="clickBtn" value="Echo Username"
onclick="alert(username.value)">
</form>
<script>
var form1 = document.getElementById('form1')
console.log(form1);
console.log(form1.username); // 打印出name为username的input元素
console.log(form1.clickBtn.value); // Echo Username
</script>
DOM0 级事件处理程序
使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this引用当前元素。
<input type="button" id="btn1" name="clickBtn" value="Echo Username">
<script>
var oBtn = document.getElementById('btn1')
// 每个元素都有自己的事件处理程序属性,这些属性通常全部小写
// 将这种属性的值设置为一个函数,就可以指定事件处理程序
oBtn.onclick = function(){
console.log(this.name) // 'clickBtn'
// 使用JavaScript 指定事件处理程序 this指向oBtn元素
// 因为事件处理程序(或者说onclick属性)是注册到这个元素上的
}
btn.onclick = null; //删除事件处理程序
</script>
DOM2 级事件处理程序
-
addEventListener()和removeEventListener()。
所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("Hello world!");
}, false);
使用DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。不会被覆盖,会根据添加的顺序相继执行。
removeEventListener()来移除事件处理程序,移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除。
IE的事件处理程序
-
attachEvent()和detachEvent()
这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数,因为IE8及更早的版本只支持事件冒泡。第一个参数事件处理程序名称字符串是需要加"on"的,例如"onclick"。也可以用来为一个元素添加多个事件处理程序,但是不是以添加它们的顺序执行,而是以相反的顺序被触发。attachEvent()添加的匿名函数也不能使用detachEvent()移除。
跨浏览器的事件处理程序
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;
}
}
};
EventUtil.addHandler(btn, "click", handler);
EventUtil.removeHandler(btn, "click", handler);
三、事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。
event对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下列通用属性。
-
bubbles:Boolean类型,表明事件是否冒泡。 -
cancelable:Boolean类型,表明是否可以取消事件的默认行为。 -
currentTarget:Element类型,其事件处理程序当前正在处理事件的那个元素。 -
preventDefault():取消事件的默认行为。如果cancelable是true,则可以使用这个方法 -
defaultPrevented:Boolean类型,为true表示已经调用了preventDefault();DOM3新增 -
stopPropagation():取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法。 -
stopImmediatePropagation():取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用;DOM3新增 -
target:Element类型,事件的目标。 -
type:String类型,被触发的事件的类型 -
eventPhase:调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标”,3表示冒泡阶段
事件处理程序中的this和currentTarget,均指向注册事件的那个元素。而target则指向的是事件目标,即事件在哪个元素上被触发。如果注册事件的元素和触发事件的元素则三个值相等。
// 点击了myBtn 以下函数会弹出3个true
document.body.onclick = function(event){
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
};
在需要通过一个函数处理多个事件时,可以使用type属性和switch语句结合,不同的类型执行不同的代码。
如果你想阻止链接导航这一默认行为,那么通过链接的"onclick"事件处理程序可以取消它
var link = document.getElementById("myLink");
link.onclick = function(e){
e.preventDefault();
};
如果你想停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event){
alert("Body clicked"); // 将不会执行。
};
IE中的事件对象
在使用DOM0 级方法添加事件处理程序(元素注册on事件)时,event对象作为window对象的一个属性存在。如果事件处理程序是使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var event = window.event;
alert(event.type); //"click"
}
btn.attachEvent("onclick", function(event){
alert(event.type); //"click"
});
IE 的event 对象的通用属性:
-
cancelBubble:Boolean类型,可读写,默认值为false,但将其设置为true就可以取消事件冒泡。 -
returnValue:Boolean类型,可读写,默认值为true,但将其设置为false就可以取消事件的默认行为。 -
srcElement:Element类型,只读,事件的目标(与target属性相同) -
type:String类型,被触发的事件的类型
四、事件类型
4.1 UI事件
User Interface(用户界面)事件,指的是那些不一定与用户操作有关的事件。以下<object>元素可以理解为<script>和<link>标签。
onload
onload:当页面完全加载后(包括所有图像、JavaScript 文件、CSS文件等外部资源)在window上面触发,当图像加载完毕时在<img>元素上面触发,或者当嵌入的内容加载完毕时在<object>元素上面触发。
我们如果想向DOM中添加一个新元素,必须确定页面已经加载完毕——如果在页面加载前操作DOM会导致错误。新图像元素不一定要被添加到文档后才开始下载,只要设置了src属性就会开始下载。与图像不同,只有在设置了<script>元素的src属性并将该元素添加到文档后,才会开始下载JavaScript 文件。
除IE8及更早版本外,都支持<script>的onload事件;IE 和Opera 支持<link>元素上的onload事件。
onunload
onunload 事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload 事件。用的最多的情况是清除引用,以避免内存泄漏。可以注册在window对象上,也可以在为<body>元素添加onunload属性来指定事件处理程序。
需要小心编写onunload事件处理程序中的代码。unload事件是在一切都被卸载之后才触发,这样在页面加载后存在的那些对象,此时就不一定存在了。此时,操作DOM节点或者元素的样式就会导致错误。
onabort
在用户停止下载过程时,如果嵌入的内容没有加载完,则在<object>元素上面触发。
onerror
当发生JavaScript错误时在window上面触发,当无法加载图像时在<img>元素上面触发,当无法加载嵌入内容时在<object>元素上面触发,或者当有一或多个框架无法加载时在框架集上面触发。
onselect
当用户选择文本框(<input>或<texterea>)中的一或多个字符时触发。
onresize
onresize:当窗口或框架的大小变化时在window或框架上面触发。
onscroll
onscroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。<body>元素中包含所加载页面的滚动条。可以通过document.documentElement / document.body的scrollLeft 和scrollTop属性 来监控到这一变化。
resize和scroll事件也会在窗口变化 / 文档被滚动期间重复被触发,所以有必要尽量保持这两个事件处理程序的代码简单。
4.2 焦点事件
焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。
-
onblur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。 -
onfocus:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。 -
onfocusin:在元素获得焦点时触发。,但它冒泡。支持这个事件的浏览器有IE5.5+、Safari 5.1+、Opera 11.5+和Chrome。 -
onfocusout:在元素失去焦点时触发。这个事件是HTML 事件blur 的通用版本。对这个事件支持的浏览器与onfocusin一样。
4.3 鼠标事件
onclick:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。这一点对确保易访问性很重要,意味onclick事件处理程序既可以通过键盘也可以通过鼠标执行。ondblclick:在用户双击主鼠标按钮(一般是左边的按钮)时触发。onmousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。onmouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。onmouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。onmousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。onmouseout:在鼠标指针位于目标元素上方,然后用户将其移入另一个元素时在目标元素触发。移入目标元素外部或者是移入目标元素的子元素也会触发。不能通过键盘触发这个事件。onmouseover:在鼠标指针位于目标元素外部或者目标元素的子元素上,然后用户将其移入目标元素时触发。不能通过键盘触发这个事件。onmouseup:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。onmousewheel:在用户滚动鼠标滚轮时触发。
页面上的所有元素都支持鼠标事件。除了mouseenter 和mouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。
只有在同一个元素上相继触发mousedown和mouseup事件,才会触发click事件;如果mousedown或mouseup中的一个被取消,就不会触发click 事件。顺序是:mousedown->mouseup->click->mousedown->mouseup->click->dblclick
鼠标事件都是在浏览器视口中的特定位置上发生的,会有相应的位置信息保存在事件对象中:
位置信息
-
e.clientX:鼠标位置在视口中的水平坐标。 -
e.clientX:鼠标位置在视口中的水平坐标。 -
e.pageX:鼠标位置在文档中的水平坐标。如果页面向右滚动了,这个值大于e.clientX,否则相等。 -
e.pageY:鼠标位置在文档中的垂直坐标。如果页面向下滚动了,这个值大于e.clientY,否则相等。 -
e.screenX:鼠标位置在整个电脑屏幕中的水平坐标。 -
e.screenY:鼠标位置在整个电脑屏幕中的垂直坐标。
修改键
-
e.shiftKey:Boolean类型,表示鼠标被按下时,是否shift键也被按下了。 -
e.ctrlKey:Boolean类型,表示鼠标被按下时,是否ctrl键也被按下了。 -
e.altKey:Boolean类型,表示鼠标被按下时,是否alt键也被按下了。 -
e.metaKey:Boolean类型,表示鼠标被按下时,是否meta键也被按下了。(在Windows键盘中是Windows键,在苹果机中是Cmd键)
相关元素
e.relatedTarget
在发生mouseover 和mouserout 事件时,还会涉及更多的元素。这两个事件都会涉及把鼠标指针从一个元素的边界之内移动到另一个元素的边界之内。对mouseover事件而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。类似地,对mouseout事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseover和mouseout事件才包含值。在其他鼠标事件触发时,这个值是null
IE8及之前版本不支持relatedTarget属性,在mouseover 事件触发时,IE 的fromElement 属性中保存了相关元素;在mouseout 事件触发时,IE 的toElement 属性中保存着相关元素。
鼠标按钮
e.button
该属性可能有如下3 个值:0 表示主鼠标按钮(一般是左键),1 表示中间的鼠标按钮(鼠标滚轮按钮),2 表示次鼠标按钮(一般是右键)。在使用onmouseup事件处理程序时,button的值表示释放的是哪个按钮。
鼠标滚轮
onmousewheel事件这可以在任何元素上面触发,最终会冒泡到document,对应的event 对象除包含鼠标事件的所有标准信息外,还包含一个特殊的wheelDelta 属性。
e.wheelDelta
当用户向前滚动鼠标滚轮时,wheelDelta是120 的倍数;当用户向后滚动鼠标滚轮时,wheelDelta是-120的倍数。通过检测wheelDelta的正负号就可以确定鼠标滚轮滚动的方向。
Opera 9.5 之前的版本中,wheelDelta 值的正负号是颠倒的(数值也是120)。Firefox 支持一个名为DOMMouseScroll的类似事件,信息则保存在detail属性中,正负号也与标准是颠倒的,当向前滚动鼠标滚轮时,这个属性的值是-3 的倍数,当向后滚动鼠标滚轮时,这个属性的值是3 的倍数。
触摸设备iOS 和Android
- 不支持
ondblclick事件。双击浏览器窗口会放大画面,而且没有办法改变该行为。 - 轻击可单击元素会触发
mousemove事件。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有因此变化,那么会依次发生mousedown、mouseup和click事件。轻击不可单击的元素不会触发任何鼠标事件。可单击的元素是指那些单击可产生默认操作的元素(如链接),或者那些已经被指定了onclick事件处理程序的元素。 -
mousemove事件也会触发mouseover和mouseout事件 - 两个手指放在屏幕上且页面随手指移动而滚动时会触发
mousewheel和scroll事件。
4.4 键盘与文本事件
-
onkeydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。 -
onkeypress:除Shift,Fn,CapsLock外任意键被按住时(连续触发)。(MDN解释) -
onkeyup:当用户释放键盘上的键时触发。
键码
e.keyCode
在IE中是e.which,keyCode属性的值与ASCII码中对应小写字母或数字的编码相同。非字符串的常用键码如下:
| 键 | 键码 | 键 | 键码 |
|---|---|---|---|
退格(Backspace) |
8 | 制表(Tab) |
9 |
回车(Enter) |
13 | 上档(Shift) |
16 |
控制(Ctrl) |
17 | Alt |
18 |
暂停/中断(Pause/Break) |
19 | 大写锁定(Caps Lock) |
20 |
退出(Esc) |
27 | 上翻页(Page Up) |
33 |
下翻页(Page Down) |
34 | 左箭头(Left Arrow) |
37 |
上箭头(Up Arrow) |
38 | 右箭头(Right Arrow) |
39 |
下箭头(Down Arrow) |
40 | 插入(Ins) |
45 |
删除(Del) |
46 | 数字锁(Num Lock) |
144 |
滚动锁(Scroll Lock) |
145 |
DOM3 级事件还是做出了一些改变,新属性:key,key属性是为了取代keyCode而新增的,它的值是一个字符串。在按下某个字符键时,key的值就是相应的文本字符('a','A','1')。在按下非字符键时,key的值是相应键的名(如“Shift”或“Down”)
textInput 事件
“DOM3 级事件”规范中引入了一个新事件textInput。根据规范,当用户在可编辑区域中输入字符时,就会触发这个事件。由于textInput事件主要考虑的是字符,因此它的event 对象中还包含一个data属性,这个属性的值就是用户输入的字符(而非字符编码)。event对象上还有一个属性,叫inputMethod,表示把文本输入到文本框中的方式。
- 0,表示浏览器不确定是怎么输入的。
- 1,表示是使用键盘输入的。
- 2,表示文本是粘贴进来的。
- 3,表示文本是拖放进来的。
- 4,表示文本是使用IME 输入的。
- 5,表示文本是通过在表单中选择某一项输入的。
- 6,表示文本是通过手写输入的(比如使用手写笔)。
- 7,表示文本是通过语音输入的。
- 8,表示文本是通过几种方法组合输入的。
- 9,表示文本是通过脚本输入的。
4.5 HTML5 事件
-
beforeunload事件
当浏览器窗口关闭或者刷新时,会触发beforeunload事件。当前页面不会直接关闭,可以点击确定按钮关闭或刷新,也可以取消关闭或刷新。
会弹出这个对话框询问用户是否离开,为了显示弹出对话框,必须将event.returnValue的值设置为要显示给用户的字符串(对IE 及Fiefox 而言),同时作为函数的值返回(对Safari 和Chrome 而言)。
window.addEventListener('beforeunload',function(e){
var e = e || window.event;
var message = "I'm really going to miss you if you go.";
event.returnValue = message;
return message
})
-
DOMContentLoaded事件
DOMContentLoaded 事件则在形成完整的DOM树之后就会触发,不理会图像、JavaScript 文件、CSS 文件或其他资源是否已经下载完毕。
-
readystatechange事件
支持readystatechange事件的每个对象都有一个readyState属性,可能包含下列5 个值中的一个。
-
uninitialized(未初始化):对象存在但尚未初始化。 -
loading(正在加载):对象正在加载数据。 -
loaded(加载完毕):对象加载数据完成。 -
interactive(交互):可以操作对象了,但还没有完全加载。 -
complete(完成):对象已经加载完毕。
如果某个阶段不适用某个对象,则该对象完全可能跳过该阶段;并没有规定哪个阶段适用于哪个对象。显然,这意味着readystatechange事件经常会少于4 次,而readyState属性的值也不总是连续的。
对于document而言,值为"interactive"的readyState 会在与DOMContentLoaded大致相同的时刻触发readystatechange 事件。
<script>(在IE 和Opera 中)和<link>(仅IE 中)元素也会触发readystatechange事件,可以用来确定外部的JavaScript 和CSS 文件是否已经加载完成。与在其他浏览器中一样,除非把动态创建的元素添加到页面中, 否则浏览器不会开始下载外部资源。基于元素触发的readystatechange 事件也存在同样的问题, 即readyState 属性无论等于"loaded" 还是"complete"都可以表示资源已经可用。
-
pageshow和pagehide事件
Firefox 和Opera 有一个特性,名叫“往返缓存”(back-forward cache,或bfcache),可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。这个缓存中不仅保存着页面数据,还保存了DOM 和JavaScript的状态;实际上是将整个页面都保存在了内存里。 如果页面位于bfcache 中,那么再次打开该页面时就不会触发load事件。尽管由于内存中保存了整个页面的状态,不触发load事件也不应该会导致什么问题,但为了更形象地说明bfcache的行为,Firefox 还是提供了一些新事件。
pageshow,这个事件在页面显示时触发,无论该页面是否来自bfcache。在重新加载的页面中,pageshow 会在load 事件触发后触发;而对于bfcache 中的页面,pageshow会在页面状态完全恢复的那一刻触发。pageshow 事件的event 对象还包含一个名为persisted 的布尔值属性。如果页面被保存在了bfcache 中,则这个属性的值为true;否则,这个属性的值为false。
pagehide事件,该事件会在浏览器卸载页面的时候触发,而且是在unload 事件之前触发。与pageshow 事件一样,pagehide 在document上面触发,但其事件处理程序必须要添加到window对象。这个事件的event对象也包含persisted属性,不过其用途稍有不同。对于pagehide 事件,如果页面在卸载之后会被保存在bfcache 中,那么persisted的值会被设置为true。
-
hashchange事件
在URL的参数列表(及URL 中“#”号后面的所有字符串)发生变化时触发。必须要把hashchange事件处理程序添加给window 对象,然后URL 参数列表只要变化就会调用它。此时的event 对象应该额外包含两个属性:oldURL 和newURL。
4.6 设备事件
-
orientationchange事件
移动Safari 中添加了orientationchange事件,以便开发人员能够确定用户何时将设备由横向查看模式切换为纵向查看模式。
移动Safari 的window.orientation 属性中可能包含3 个值:0 表示肖像模式,90 表示向左旋转的横向模式(“主屏幕”按钮在右侧),-90 表示向右旋转的横向模式(“主屏幕”按钮在左侧)。
只要用户改变了设备的查看模式,就会触发orientationchange 事件。此时的event 对象不包含任何有价值的信息,因为唯一相关的信息可以通过window.orientation 访问到。
4.7 触摸与手势事件
移动Safari提供了一些与触摸(touch)操作相关的新事件。后来Android 上的浏览器也实现了相同的事件。
-
touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。 -
touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault()可以阻止滚动。 -
touchend:当手指从屏幕上移开时触发。
上面这几个事件都会冒泡,也都可以取消。每个触摸事件的event 对象都提供了在鼠标事件中常见的属性外还包含三个用于跟踪触摸的属性。
-
touches:表示当前跟踪的触摸操作的Touch对象的数组。 -
targetTouchs:特定于事件目标的Touch对象的数组。 -
changeTouches:表示自上次触摸以来发生了什么改变的Touch对象的数组。
每个Touch 对象包含下列属性。
-
clientX:触摸目标在视口中的x 坐标。 -
clientY:触摸目标在视口中的y 坐标。 -
identifier:标识触摸的唯一ID。 -
pageX:触摸目标在页面中的x 坐标。 -
pageY:触摸目标在页面中的y 坐标。 -
screenX:触摸目标在屏幕中的x 坐标。 -
screenY:触摸目标在屏幕中的y 坐标。 -
target:触摸的DOM节点目标。
function handleTouchEvent(event){
//只跟踪一次触摸
if (event.touches.length == 1){
var output = document.getElementById("output");
switch(event.type){
case "touchstart":
output.innerHTML = "Touch started";
break;
case "touchend":
output.innerHTML += "<br>Touch ended (" +
event.changedTouches[0].clientX + "," +
event.changedTouches[0].clientY + ")";
break;
case "touchmove":
event.preventDefault(); //阻止滚动
output.innerHTML += "<br>Touch moved (" +
event.changedTouches[0].clientX + "," +
event.changedTouches[0].clientY + ")";
break;
}
}
}
以上代码会跟踪屏幕上发生的一次触摸操作。当touchmove 事件发生时,会取消其默认行为,阻止滚动(触摸移动的默认行为是滚动页面),在touchend 事件发生时,touches集合中就没有任何Touch 对象了,因为不存在活动的触摸操作;此时,就必须转而使用changeTouchs集合。
iOS 2.0 中的Safari 还引入了一组手势事件。当两个手指触摸屏幕时就会产生手势,手势通常会改变显示项的大小,或者旋转显示项。
-
gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。 -
gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。 -
gestureend:当任何一个手指从屏幕上面移开时触发。
只有两个手指都触摸到事件的接收容器时才会触发这些事件。在一个元素上设置事件处理程序,意味着两个手指必须同时位于该元素的范围之内,才能触发手势事件件冒泡,所以将事件处理程序放在文档上也可以处理所有手势事件。此时,事件的目标就是两个手指都位于其范围内的那个元素。
触摸事件和手势事件之间存在某种关系。当一个手指放在屏幕上时,会触发touchstart 事件。如果另一个手指又放在了屏幕上,则会先触发gesturestart 事件,随后触发基于该手指的touchstart事件。如果一个或两个手指在屏幕上滑动,将会触发gesturechange 事件。但只要有一个手指移开,就会触发gestureend 事件,紧接着又会触发基于该手指的touchend 事件。
每个手势事件的event对象都包含着标准的鼠标事件属性,也包含两个额外的属性。
rotation属性表示手指变化引起的旋转角度,负值表示逆时针旋转,正值表示顺时针旋转(该值从0 开始)scale属性表示两个手指间距离的变化情况(例如向内收缩会缩短距离);这个值从1 开始,并随距离拉大而增长,随距离缩短而减小。
触摸事件也会返回rotation 和scale 属性,但这两个属性只会在两个手指与屏幕保持接触时才会发生变化。
五、内存和性能
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。事实上,从如何利用好事件处理程序的角度出发,还是有一些方法能够提升性能的。
事件委托
使用事件委托,只需在DOM 树中尽量最高的层次上添加一个事件处理程序。在通过e.target属性得到目标元素,然后做出相应的操作。
function(event){
var target = event.target || event.srcElement;
switch(target.id){
case "doSomething":
document.title = "I changed the document's title";
break;
case "goSomewhere":
location.href = "http://www.wrox.com";
break;
case "sayHi":
alert("hi");
break;
}
}
在这段代码里,我们使用事件委托只为<ul>元素添加了一个onclick 事件处理程序。也只取得了一个DOM 元素,对用户来说最终的结果相同,但这种技术需要占用的内存更少。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术。
移除事件处理程序
当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript 代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立的连接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案
在两种情况下,可能会造成上述问题。第一种情况就是从文档中移除带有事件处理程序的元素时。这可能是通过纯粹的DOM操作,例如使用removeChild()和replaceChild()方法,但更多地是发生在使用innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被innerHTML 删除了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。
如果你知道某个元素即将被移除,那么最好手工移除事件处理程序。例如:btn.onclick = null;、removeEventListener() 、detachEvent()或者使用事件委托将事件注册在尽量高的层次的元素上。
注意,在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中是事件冒泡的前提。
另一种情况,就是卸载页面的时候,如果在页面被卸载之前没有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页面间来回切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序占用的内存并没有被释放。一般来说,最好的做法是在页面卸载之前,先通过onunload 事件处理程序移除所有事件处理程序。
使用onunload 事件处理程序意味着页面不会被缓存在bfcache 中。如果你在意这个问题,那么可以使用别的方式来移除事件。
六、模拟事件
事件经常由用户操作或通过其他浏览器功能来触发。但很少有人知道,也可以使用JavaScript 在任意时刻来触发特定的事件,而此时的事件就如同浏览器创建的事件一样。也就是说,这些事件该冒泡还会冒泡,而且照样能够导致浏览器执行已经指定的处理它们的事件处理程序。
DOM中的事件模拟
-
createEvent()和dispatchEvent()
可以在document 对象上使用createEvent()方法创建event 对象。这个方法接收一个参数,即表示要创建的事件类型的字符串。在DOM2 级中,所有这些字符串都使用英文复数形式,而在DOM3级中都变成了单数。
-
UIEvents:一般化的UI 事件。鼠标事件和键盘事件都继承自UI 事件。 -
MouseEvents:一般化的鼠标事件。 -
MutationEvents:一般化的DOM 变动事件。 -
HTMLEvents:一般化的HTML 事件。没有对应的DOM3 级事件
模拟事件的最后一步就是触发事件。这一步需要使用dispatchEvent()方法,所有支持事件的DOM 节点都支持这个方法。调用dispatchEvent()方法时,需要传入一个参数,即表示要触发事件的event 对象。
模拟鼠标事件
创建鼠标事件对象的方法是为createEvent()传入字符串"MouseEvents"。返回的事件对象有一个名为initMouseEvent()方法,用于指定与该鼠标事件有关的信息。这个方法接收15个参数。
event.initMouseEvent(type, canBubble, cancelable, view,
detail, screenX, screenY, clientX, clientY,
ctrlKey, altKey, shiftKey, metaKey,
button, relatedTarget);
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);
模拟键盘事件
event.initKeyEvent (type, bubbles, cancelable, viewArg,
ctrlKeyArg, altKeyArg, shiftKeyArg,
metaKeyArg,keyCodeArg, charCodeArg)
//只适用于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);
自定义事件
event.initCustomEvent (type, bubbles, cancelable, detail)
var div = document.getElementById("myDiv");
var event;
EventUtil.addHandler(div, "myevent", function(event){
alert("DIV: " + event.detail);
});
EventUtil.addHandler(document, "myevent", function(event){
alert("DOCUMENT: " + event.detail);
});
if (typeof document.createEvent == 'function'){
event = document.createEvent("CustomEvent");
event.initCustomEvent("myevent", true, false, "Hello world!");
div.dispatchEvent(event);
}
IE中的事件模拟
-
document.createEventObject()和fireEvent()
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);
