一、事件流
IE的事件流是事件冒泡,Nestcape Communicator事件流是事件捕获
1.1、事件冒泡
IE的事件流叫事件冒泡,即事件开始时由最具体的元素接收,然后逐渐向上传播到较为不具体的节点
1.2、事件捕获
事件捕获的思想是不太具体的节点应该是更早接收到事件,而最具体的节点应该最后受到事件,事件捕获的用意在于事件达到预定目标之前捕获它
1.3、DOM事件流
DOM2级事件流包括三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段
二、事件处理程序
相应某个事件的函数就是事件处理程序(或事件监听器),事件处理程序的名字以’on’开头,因此click的事件处理程序就是onclick
2.1、HTML事件处理程序
//方法一:
<input type="button" value="Click Me" onclick="console.log('Clicked')">
//方法二:
<input type="button" value="Click Me" onclick="showMessage()">
function showMessage(){
console.log('Clicked')
}
第二种方法具有一些独到之处,首先,这样会创建一个封装着元素值的函数,这个函数有一个局部变量evevt,也是事件对象
<input type="button" value="Click me" onclick="console(event.type)">
通过event对象,可以直接访问事件对象,不用自己定义它,也不用从函数的参数列表中读取,在这个函数内部,this的值等于事件的目标元素
<input type="button" value="Click Me" onclick="console.log(this.value)">
关于这个动态创建的函数,另有一个有意思的就是可以扩展作用域
<input type="button" value="Click Me" onclick="showMessage()">
function showMessage(){
var input=document.getElementsByTagName("input")[0]
with(document){
with(input){
console.log(value);
}
}
}
但是为了捕获错误,一般会将HTML事件处理程序封装在一个try-catch块中
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">
2.2、DOM0级事件处理程序
DOM0级事件方法指定的事件处理程序被认为是元素的方法,因此,这时候的事件处理程序是在元素的作用域中运行,换句话说,程序中的this引用当前元素
var bth=document.getElementById("myBtn");
btn.onclick=function(){
console.log(this.id)//myBtn
}
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理
移除事件处理程序
btn.onclick=null
2.3、DOM2级事件处理程序
DOM2级事件定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener(),所有的DOM节点都包含这两个方法,并且他们都接受三个参数:要处理的事件名,作为事件处理程序的函数和一个布尔值,布尔值为true表示在捕获阶段调用事件处理程序,为false表示在冒泡阶段处理
var btn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
console.log(this.id)//myBtn
},false)
使用DOM2级方法添加事件的主要好处就是可以添加多个事件处理程序
var btn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
console.log(this.id)//myBtn
},false)
btn.addEventListener("click",function(){
console.log("HEllo")
},false)
通过addEventLinister()添加的事件处理程序只能通过removeEventLinister()来移除,移除时传入的参数与添加程序时使用的参数相同,这也就意味着addEventLinister添加的匿名函数无法移除
var btn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
console.log(this.id)//myBtn
},false)
btn.removeEventListener("click",function(){ console.log(this.id)
},false)
虽然调用removeEventLinister()时使用了看似相同的函数,但是实则完全不同,要想传入的参数相同,则
var btn=document.getElementById("myBtn");
var handle=function(){
console.log(this.id)//myBtn}
btn.addEventListener("click",handle,false)
btn.removeEventListener("click",handle,false)
2.4、IE事件处理程序
实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接收相同的参数:事件处理程序名称与事件处理程序函数,IE8以前的版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段
var btn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
console.log("Clicked")
})
在IE中使用attachEvent()和在DOM0中的方法主要区别在于事件处理程序的作用域,在使用DOM0级的情况下,事件处理程序在所属元素的作用域内进行;在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this等于window
var btn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
console.log(this===window)//true
})
同样attachEvent()也可以为一个元素添加多个事件处理程序
var btn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
console.log(this.id)//myBtn
},false)
btn.aattachEvent("conlick",function(){
console.log("HEllo")
},false)
同样,使用移除事件处理程序的函数也和DOM2中相同
var btn=document.getElementById("myBtn");
var handle=function(){
console.log(this.id)//myBtn}
btn.attachEvent("onclick",handle,false)
btn.detachEvent("onclick",handle,false)
2.5、跨浏览器的事件处理程序
var EventUtil={
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false)//dom2
}else if(element.attachEvent){
element.attachEvent("on"+type,handler,false)//ie }else{
element["on"+type]=handler//dom0
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false)
}else if(element.detachEvent){
element.detachEvent("on"+type,handler,false)
}else{
element["on"+type]=null
}
}
}
三、事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件相关的信息。
3.1、DOM中的事件对象
var btn=document.getElementById("myBtn");
btn.onclick=function(event){
console.log(event.type)
}
所有的事件都会有如下的属性或者方法(只列出了常用的)
cancelTarget:表示是否取消事件的默认行为
pereventDefault():取消事件默认行为,如果cancelable为true,则可以使用这个方法
stopPropagation():取消事件的进一步捕获或者冒泡,如果bubbles为true,则可以使用这个方法
target:事件的目标
currentTarget:其事件处理程序当前正在处理事件的那个元素
在事件处理程序的内部,对象this始终等于currenttarget的值,而target只包含事件的实际目标
btn.onclick=function(event){
console.log(event.currentTarget===this)//true
console.log(event.target===this)//true }
如果事件处理程序存在其父节点中,则
document.body.onclick=function(event){
console.log(event.currentTarget===this)//true
console.log(event.target==document.getElementById("myBtn"))//true
console.log(event.target===this)//false
}
在需要通过一个函数处理多个事件时,可以使用type属性
var handler=function(event){
switch(event.type){
case "click":
console.log("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
要想阻止其默认行为,可以使用preventDefault()
var a=document.getElementsByTagName("a")[0]
a.onclick=function(event){
event.preventDefault()
}
stopPropagation()用于立即停止事件在DOM层次中的传播,取消事件捕获或者冒泡
btn.onclick=function(event){
console.log("Click");
event.stopPropagation();
}
document.body.onclick=function(event){
console.log("Body clicked") }
只会弹出Click,阻止了向document.body冒泡
对象的eventPhase属性,可以用来确定当前事件正位于事件流的那个阶段,如果在捕获阶段调用事件处理程序,那么eventPhase等于1,如果事件处理程序处于目标上,则为2,
如果在冒泡阶段调用事件处理程序,则为3
btn.onclick=function(event){
console.log(event.eventPhase);//2
}
document.body.addEventListener("click",function(){
console.log(event.eventPhase)//1
},true)
document.body.onclick=function(event){
console.log(event.eventPhase)//3
}
3.2、IE中的事件对象
在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在
btn.onclick=function(){
var event=window.event;
console.log(event.type)//click
}
但是如果事件处理程序是通过IE事件处理程序添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中
btn.attachEvent("onclick",function(event){
console.log(event.type)//click
})
同样,IE的event对象同样也包含与创建它的事件相关的属性和方法。
cancelBabel :默认值为false,设置为true就可以取消事件冒泡的(与DOM中stopPropagation()相同)
returnValue:默认值为true,设置为false可以取消事件的默认行为(同preventDefault())
srcElement:时间的目标
type:被触发事件的类型
因为事件处理程序的作用域是根据他的方式来确定的,所以不能认为this始终等于事件目标
btn.onclick=function(){
console.log(window.event.srcElement===this)//true
}
btn.attachEvent("onclick",function(event){
console.log(event.srcElement===this)//false
})
所以用event.srcElement比较保险
阻止事件的默认行为returnValue属性
var a=document.getElementsByTagName("a")[0];
a.onclick=function(){
window.event.returnValue=false }
同样,cancelBuble属性与DOM中stopPropagation()方法相同。
btn.onclick=function(){
console.log("CLick");
window.event.cancelBubble=true
}
document.body.onclick=function(){
console.log("Body Clicked") }
只会出现Click
3.3、跨浏览器的事件对象
var EventUtil={
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false)//dom2
}else if(element.attachEvent){
element.attachEvent("on"+type,handler,false)//ie
}else{
element["on"+type]=handler//dom0
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false)
}else if(element.detachEvent){
element.detachEvent("on"+type,handler,false)
}else{
element["on"+type]=null
}
},
getEvent:function(event){
return event?event:window.event
},
getTarget:function(event){
return event.target||event.srcTarget
},
preventDefault:function(event){
if(event.preventDefault){
event.preventDefault()
}else{
event.returnValue=false;
}
},
stopPropagation:function(event){
if(event.stopPropagation){
event.stopPropagation()
}else{
event.cancelBubble=true;
}
}
}
使用方法
var a=document.getElementsByTagName("a")[0];
a.onclick=function(event){
event=EventUtil.getEvent(event); //获得event对象
EventUtil.preventDefault(event)
}
四、事件类型
4.1、UI事件
4.1.1、load事件
当页面完全加载后,就会触发window上面的load事件
有两种定义onload事件处理程序的方式
//第一种
EventUtil.addHandler(window,"load",function(event){ console.log("Loaded")})
//第二种
<body onload="console.log('Loaded')"
加载script和link
EventUtil.addHandler(window,"load",function(){
var script=document.createElement("script");
EventUtil.addHandler(script,"load",function(event){
console.log("Loaded");
})
script.src="./example.js";
document.body.appendChild(script)
var link=document.createElement("link");
link.type="text/css";
link.rel="stylesheet"
EventUtil.addHandler(link,"load",function(event){
console.log("CSS Loaded")
})
link.href='./example.css';
document.getElementsByTagName("head")[0].appendChild(link)
})
4.1.2、onload事件
unload是事件在文档被完全卸载后触发
同样与load事件一样,也有两种指定事件处理程序的方法
4.1.3、resize事件
当浏览器窗口被调整到一个高度时,就会触发resize事件,这个事件是在window上面触发,所以可以使用javascript或者元素中的onresize特性来指定事件处理程序(因为HTML元素中不能在window上触发)
4.1.4、scroll事件
虽然scroll事件是在window对象上发生的,但它实际表示的则时页面中相应元素的变化
在混杂模式下,可以通过元素的scrollLeft和scrollTop来监控到这一变化;而在标准模式下,除了Safari之外的所有浏览器都会通过元素来反映这一变化
EventUtil.addHandler(window, "scroll", function(event){
if (document.compatMode == "CSS1Compat"){
console.log(document.documentElement.scrollTop);
} else {
console.log(document.body.scrollTop);
}
});
4.2、焦点事件
blur:在元素失去焦点时触发。这个事件不会冒泡,所有的浏览器都支持
DOMFocusIn:在元素获得焦点时触发
DOMFocusOut:在元素失去焦点时触发
focus:在元素获得焦点时触发,这个事件不会冒泡,所有的浏览器都支持
focusin:在元素获得焦点时触发,这个事件与HTML的focus等价
focusout:在元素失去焦点时触发,这个事件与HTML的blur等价
4.3、鼠标与滚轮事件
click,dbclick,mouseup,mousedown,mouseenter,mouseleave,mouseover,mouseout,mousemove
其中mouseenter,在鼠标光标从元素外部首次移动到元素范围之内时触发,这个事件不冒泡,而且移动到其后代上时不触发
mouseover,在鼠标指针位于一个元素的外部,然后用户将其首次移入到另一个元素边界之内时触发
4.3.1、客户区坐标位置
表示事件发生时鼠标指针在视口中的水平和垂直坐标
var div=document.getElementById("myDiv")
EventUtil.addHandler(div,"click",function(event){
event=EventUtil.getEvent(event);
console.log("Client coordinate:"+event.clientX+","+event.clientY)
})
4.3.2、页面坐标的位置
页面坐标通过pageX和pageY属性确定,换句话说,这两个属性表示鼠标光标在页面中的位置
var div=document.getElementById("myDiv")
EventUtil.addHandler(div,"click",function(event){
event=EventUtil.getEvent(event);
console.log("Client coordinate:"+event.clientX+","+event.clientY)
console.log("Page coordinate:"+event.pageX+","+event.pageY)})
在没有滚动的情况下
由滚动的情况下
IE8以及更早的版本不支持对象上的页面坐标,不过使用客户区坐标和滚动信息可以计算出来
var body=document.getElementsByTagName("body")[0];
EventUtil.addHandler(body,"click",function(event){
event=EventUtil.getEvent(event);
var pageX=event.pageX,
pageY=event.pageY
if(pageX===undefined){
pageX=event.clientX+(document.body.scrollLeft||document.documentElement.scrollLeft) }
if(pageY===undefined){
pageY=event.clientY+(document.body.scrollTop||document.documentElement.scrollTop) }
console.log("Page coordinates:"+pageX+","+pageY)
})
4.3.3、屏幕坐标位置
鼠标事件发生时,不仅会有相对于浏览器窗口的位置,还有一个相对于整个电脑屏幕的位置。通过scrollX和scrollY属性就可以确定
var div=document.getElementById("myDiv")
EventUtil.addHandler(div,"click",function(event){
event=EventUtil.getEvent(event);
console.log("Client coordinate:"+event.clientX+","+event.clientY)
console.log("Page coordinate:"+event.pageX+","+event.pageY)
console.log("Screen coordinate:"+event.screenX+","+event.screenY)
})
4.4.4、修改键
shift,Ctrl,Alt,Meta
这些属性包含的都是布尔值,如果相应的键被按下了,则返回true,否则返回false
var body=document.getElementsByTagName("body")[0]
EventUtil.addHandler(body,"click",function(event){
event=EventUtil.getEvent(event);
var key=new Array()
if(event.shiftKey){
key.push("shift")
}else if(event.ctrlKey){
key.push("ctrl")
}else if(event.altKey){
key.push("alt")
}else if(event.metaKey){
key.push("meta")
}
console.log("Key:"+key.join(","))
})
4.4.5、相关元素
在发生mouseover和mouseout事件时,这两个事件都会涉及把鼠标指针从一个元素移动到另一个元素的边界之内。
对mouseover元素来说,事件的主元素时获得光标的元素,而相关元素是失去光标的元素
对mouseout元素来说,事件的主元素时失去光标的元素,而相关元素是得到光标的元素
DOM通过event对象的relatedTarget属性提供了相关元素的信息,这个元素只对于mouseover和mouseout事件才包含的值,对于其他事件,这个属性值为null
IE8之前不支持relatedTarget属性,但是在mouseover事件触发时,IE的fromElement属性中保存了相关的元素;在mouseout事件触发时,IE的toElement属性中保存了相关的元素
getRelatedTarget:function(event){
if(event.relatedTarget){
return event.relatedTarget
}else if(event.toElement){
return event.toElement
}else if(event.fromElement){
return event.fromElement
}else{
return null;
}
}
var div=document.getElementById("myDiv");
EventUtil.addHandler(div,"mouseout",function(event){
event=EventUtil.getEvent(event);
var target=EventUtil.getTarget(event);
var relatedTarget=EventUtil. getRelatedTarget(event);
console.log("Mouses out of "+target.tagName+" to "+relatedTarget.tagName)
})
结果如下:
4.4.6、鼠标按钮
对于mouseout和mousedown事件来说
DOM下button属性有三个值:0表示主鼠标按钮,1表示中间的鼠标按钮,2表示右边的鼠标按钮
IE8之前则有很大的差异
兼容性的写法为
getButton:function(event){
if(document.implementation.hasFeature("MouseEvents","2.0")){
return event.button}else{
switch(event.button){
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4: return 1;
}
}
}
用法
var div=document.getElementById("myDiv");
EventUtil.addHandler(div,"mousedown",function(event){
event=EventUtil.getEvent(event);
console.log(EventUtil.getButton(event))
})
4.4.7、更多的事件信息
detail,用于给出事件的更多信息
4.6.8、鼠标滚轮事件
mousewhee事件,这个事件可以在任何元素上触发,最终被冒泡到doucment(IE8)或者window(IE9,opera,chrom,Safair),与mousewheel事件对应的属性wheelDetla,当鼠标向前滚动时,wheelDetla时120的整数倍,相反,是-120的整数倍。
EventUtil.addHandler(document,"mousewheel",function(event){
event=EventUtil.getEvent(event);
console.log(event.wheelDelta)
})
在opera9.5以前,wheelDelta的正负号是颠倒的,兼容性写法
EventUtil.addHandler(document,"mousewheel",function(event){
event=EventUtil.getEvent(event);
var delta=(client.engine.opera && client.engine.opera<9.5?-event.wheelDelta:event.wheelDelta)
console.log(delta)
})
Firefox支持一个名为DOMMouseScroll的类似事件,也是在鼠标滚动的情况下触发,向前滚动鼠标是,这个属性是-3的倍数,向后滚动时,是3的倍数,信息保存在detail属性中
EventUtil.addHandler(document,"DOMMouseScroll",function(event){
event=EventUtil.getEvent(event);
console.log(event.detail)
})
兼容性写法
getWheelDelta:function(event){
if(event.wheelDelta){
return (client.engine.opera && client.engine.opera<9.5?-event.wheelDelta:event.wheelDelta)
}else{
return -event.detail*40
}
}
4.4.9、触摸设备
4.4.10、无障碍性问题
4.4、键盘与文本事件
对键盘事件的支持主要遵循的是DOM0级
有三个键盘事件:
keydown:当用户按下键盘上任意键时触发
keyup:当用户释放键盘上的键时触发
keypress:当用户按下键盘上的字符键时触发
只有一个文本事件:textInput,这个时间是对keypress的补充,用意是在将文本显示给用户之前更容易拦截文本,在文本插入文本框之前会触发textInput事件
4.4.1、键码
在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个代码,与键盘上的一个特定的键对应。对于数字字母字符键,keyCode属性的值与ASCII码中对应的小写字母或数字的编码相同
var textbox=document.getElementById("myText");
EventUtil.addHandler(textbox,"keyup",function(event){
event = EventUtil.getEvent(event);
console.log(event.keyCode)
})
4.4.2、字符编码
IE9,Firefox,Chrome,Safair地event对象都支持一个charCode属性,这个属性只有发生keypress事件时才包含值,而且这个值是按下地那个键所代表字符的ASCII编码。IE8之前版本和Opera则是在keyCode中保存字符的ASCII编码
兼容性代码如下
getCharCode:function(event){
if(typeof event.charCode=="number"){
return event.charCode
}else{
return event.keyCode
}
}
4.4.3、textInput事件
当用户在可编辑区域中输入字符时,就会触发这个事件。但是这个代替keypress的事件与yexyInput有不同之处,
其一,
任何可以获得焦点的元素都可以触发keypress事件,但只有可编辑区域才能触发textInput事件;
其二、textInput事件只会在用户按下能够输入实际字符的键才会被触发,而keypress则在按下那些能够影响文本显示的键时也会触发
textInput事件的event对象中还有一个属性data
var textbox=document.getElementById("myText");
EventUtil.addHandler(textbox,"textInput",function(event){
event = EventUtil.getEvent(event);
console.log(event.data)
})
另外,event对象上还有一个属性inputMethod,表示把文本输入到文本框中的方式
4.4.4、设备中的键盘事件
4.5、复合事件
复合事件是DOM3级事件中新添加的一类事件,用于处理IME的输入序列。IME可以让用户输入在物理键盘上找不到的字符。例如,使用拉丁文键盘的用户通过IME输入日文。IME通常需要按住多个键,但最终只输入一个字符
以下有三个复合事件
compositionstart:在IME的文本复合系统打开时触发,表示要开始输入了
compositionupdtae:在向输入字段中插入新的字符时触发
compositionend:在IME的文本复合系统关闭时触发,表示要返回正常键盘输入状态
4.4、变动事件
DOM2级的变动事件能在DOM中的某一部分发生变化时给出提示。
DOMSubtreeModified:在DOM结构中发生任何变化时触发。
DOMNodeInserted:在一个节点作为子节点被插入到另一个节点时触发
DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或者通过子树间接插入文档之后触发
DOMNoderemoveFromDocument:在一个节点被直接从文档移除或者通过子树间接从文档中移除之前触发
DOMAttrModified:在特性被修改之后触发
DOMCharacterDataModified:在文本节点的值发生变化时触发
由于DOM3级事件模块作废了很多变动事件,所以本节只介绍支持的事件
4.4.1、删除节点
- 首先触发DOMNodeRemoved事件,这个时间的目标(event.target)是被删除的节点,而event.relateNode属性中包含着对目标节点的父节点的引用,在这个节点触发时,节点尚未从父节点中删除,这个事件会冒泡,因此可以在任何DOM层次上处理它
- 如果被移除的节点包含子节点,那么在其所有的子节点以及这个被移除的节点上会相继触发DOMNodeRemovedFromDocument事件,这个事件不会冒泡,所以只有直接指定给其中的一个子节点的事件处理程序才会被调用
- 最后触发的是DOMSubtreeModified事件。这个事件的目标是被移除节点的父节点
EventUtil.addHandler(window,"load",function(event){
var list=document.getElementById('myList');
EventUtil.addHandler(document,"DOMSubtreeModified",function(event){
console.log(event.type)
console.log(event.target)
})
EventUtil.addHandler(document,"DOMNodeRemoved",function(event){
console.log(event.type)
console.log(event.target)
console.log(event.relatedNode)
})
EventUtil.addHandler(list.firstChild,"DOMNodeRemovedFromDocument",function(event){
console.log(event.type)
console.log(event.target)
})
list.parentNode.removeChild(list)})
4.4.2、插入节点
- 首先会触发DOMNodeInserted事件,这个事件的目标是被插入的节点,而event.relateNode属性中包含一个父节点的引用,在这个事件触发时,节点已经被插入到新的父节点中,这个事件会冒泡,因此可以在任何DOM层次上处理它
- 紧接着,会在新插入的节点上面触发DOMNodeInsertedIntoDocument事件。这个事件不会冒泡,因此必须在插入节点之前为他添加事件处理程序。这个事件的目标是被插入的节点
- 最后一个触发的是DOMSubtreeModified,触发于新插入的节点的父节点
EventUtil.addHandler(window,"load",function(event){
var list=document.getElementById('myList');
var item=document.createElement("li");
item.appendChild(document.createTextNode("Item 4"))
EventUtil.addHandler(document,"DOMSubtreeModified",function(event){
console.log(event.type)
console.log(event.target)
})
EventUtil.addHandler(document,"DOMNodeInserted",function(event){
console.log(event.type)
console.log(event.target)
console.log(event.relatedNode)
})
EventUtil.addHandler(item,"DOMNodeInsertedIntoDocument",function(event){
console.log(event.type)
console.log(event.target)
})
list.appendChild(item)
})
4.5、HTML5事件
4.5.1、contentmenu事件
用于表示何时应该显示上下文菜单,以便开发人员取消默认的上下文菜单而提供的自定义菜单,这个事件是冒泡的
这个事件的目标元素是发生用户操作的元素,在所有的浏览器中都可以取消这个事件:在兼容的DOM浏览器中,使用event.preventDefault();在IE中,使用event.returnValue()来取消
EventUtil.addHandler(window,"load",function(event){
EventUtil.addHandler(document,"contextmenu",function(event){
event=EventUtil.getEvent();
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"
})
})
4.5.2、beforeunload事件
为了让开发人员有可能在页面卸载之前阻止这一操作
为了显示这个弹出对话框,必须将event.returnValue的值设置为要显示给用户的·字符串(对IE以及Fiefox而言),同时作为函数的值返回(对Safair和Chrom而言)
EventUtil.addHandler(document,"beforeunload",function(event){
event=EventUtil.getEvent(event);
var message="I'm rellay going to miss you if you go";
event.returnValue=message;
return message;
})
4.5.3、DOMContentLoaded事件
DOMContentLoaded事件在形成完整的DOM树之后就会触发,不理会图像,js文件,css文件或者其他资源是否已经下载完毕
要处理DOMContentLoaded事件,可以为document或者window添加相应的事件处理程序
EventUtil.addHandler(document,"DOMContentLoaded",function(event){
console.log("Content loaded")
})
IE9+,Firefox,Chrome,Safair 3.1+和opera 9+都支持DOMContentLoaded事件
对于不支持DOMContentLoaded事件的浏览器,我们建议在页面加载期间设置一个时间为0毫秒的超时调用
setTimeout(function(){
},0)
这段代码的意思是,在当前javascript处理完成之后立即运行这个函数,在页面下载和构建期间,只有一个javascrip处理过程,因此超时调用会在该过程结束时立即触发
4.5.4、readystatechange事件
这个事件的目的是提供与文档或元素的加载状态有关的信息
这个事件的每个对象都有以下五个属性:
uninitialized(未初始化):对象存在但未初始化
loading(正在加载):对象正在加载数
loaded(加载完毕):对象加载数据完毕
interactive(交互):可以操作对象了,但还没有完全加载
complete(完成):对象已经加载完毕了
为了尽可能地抢占先机,有必要同时检测交互和完成阶段,
EventUtil.addHandler(document,"readystatechange",function(event){
if(document.readyState=="interactive"||document.readyState=="complete"){
EventUtil.removeHandler(document,"readystatechange",arguments.callee);
console.log("Content loaded")
}
})
对于上面的代码,当readystatechange事件触发时,会检测到document.readystate的值,看当前是否处于交互或者完成阶段,如果是,则移除相应的事件处理程序以免在其他阶段在执行
同样(IE)和
EventUtil.addHandler(window, "load", function(){
//create a new <script/> element.
var script = document.createElement("script");
EventUtil.addHandler(script, "readystatechange", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.readyState == "loaded" || target.readyState == "complete"){
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
console.log("Script Loaded");
}
});
script.src = "example.js";
document.body.appendChild(script);
var link = document.createElement("link"); link.rel="stylesheet";
link.type="text/css" EventUtil.addHandler(link, "readystatechange", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.readyState == "loaded" || target.readyState == "complete"){
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
console.log("Script Loaded");
}
});
link.href = "example.css";
document.getElementsByTagName("head")[0].appendChild(link)
})
在IE中
4.5.5、pageshow和pagehide事件
Firefox和Opera有一个特性,名叫“往返缓存”(bfcache)
pagweshow会在页面显示时触发,无论事件是否来自bfcache。在重新加载的页面中,pageshow会在load事件触发后触发;对于bfcache中的页面,pageshow会在页面状态完全恢复的那一刻触发
另外,这个事件的目标虽然是document,但是要将其事件处理程序添加到window
除此属性外,pageshow事件的event对象还有一个属性persisted的布尔值属性,如果页面被保存在了bfcache中,则为true,否则,为false
(function(){
var showCount = 0;
EventUtil.addHandler(window, "load", function(){
console.log("Load fired");
});
EventUtil.addHandler(window, "pageshow", function(event){
showCount++;
console.log("Show has been fired " + showCount + " times. Persisted? " + event.persisted);
});
EventUtil.addHandler(window, "pagehide", function(event){
console.log("Hiding. Persisted? " + event.persisted);
});
})();
pagehide事件会在浏览器卸载页面的时候触发,而且是在unload事件之前触发
4.5.6、hashchange事件
HTML5新增的事件,之所以要增加这个事件,是因为在Ajax中,开发人员经常要用URL参数列表来保存状态或者导航信息。
必须要把haschange事件处理程序添加给window对象,然后ul参数列表只要变化就会调用它。此时event对象包含额外的两个属性:oldURL和newURL
<ul>
<li><a href="#up">Up</a></li>
<li><a href="#down">Down</a></li>
</ul>
EventUtil.addHandler(window,"haschange",function(event){
console.log("Old URL"+event.oldURL+"new URL"+event.newURL) })
支持的浏览器有 IE8,Firefox3.6+,safair 5+,Chrome和Opera 10.6+,但是只有Firefox6.0+ chrome和opera支持oldURL和newURL,为此,最好使用location对象来确定当前的参数列表
EventUtil.addHandler(window,"hashchange",function(event){
console.log("Current hash:"+location.hash)
})
检测浏览器是否支持hashchange事件
var isSuppored=("onhashchange" in window)&&(document.documentMode===undefined||document.docmentMode>7)
4.6、设备事件
4.6.1、orientationchange事件
苹果公司为移动Safair中添加了orientationchange事件,以便开发人员能够确定用户何时将设备由横向查看模式切换为纵向查看模式
window.orientation属性中可能包含三个值:0表示肖像模式,90表示向左旋转横向模式,-90表示向右旋转横向模式
4.6.2、MozOrientation事件
Firefox 3.6为了检测设备的防线引入了一个名为MozOrientation的新事件
4.6.3、deviceorientation事件
4.6.4、devicemotion事件
4.7、触摸与手势事件
4.7.1、触摸事件
4.7.2、手势事件
五、内存和性能
5.1、事件委托
对“事件处理程序过多”问题的解决方案就是事件委托,时间委托利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click事件会一直冒泡到document层次,也就是说,我们可以为整个页面这顶一个onclick事件处理程序,而不必给每个元素分别添加事件处理程序
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
var list=document.getElementById("myLinks");
EventUtil.addHandler(list,"click",function(event){
event=EventUtil.getEvent(event);
var target=EventUtil.getTarget(event)
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;
}
})
这种做法相对于传统的做法有如下优点:
- document对象很快就可以访问,而且可以在页面生命周期的任何时点上为他添加事件处理程序。换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能
- 在页面上设置事件处理程序所需的事件更少,只添加一个事件处理程序所需的DOM引用更少,所花的时间也更少
- 整个页面占用的内存空间更少,能够提升整体的性能
最适合采用事件委托的事件包括click,mousedown,mouseup,keydown,keyup,keypress
5.2、移除事件处理程序
每当事件处理程序指定给元素时,运行中的浏览器代码与支持的页面交互的Javascript代码之间就会建立一个连接,这种连接越多,页面执行起来就越慢。前面的时间委托,限制连接数量是一种方法,另外,在不需要的时候移除事件处理程序,也是一种方案。内存中哪些过时不用的叫“空事件处理程序”,导致“空事件处理程序”有两个原因:
- 第一种情况是从文档中移除带有事件处理程序的元素时
如果页面中使用来替换页面中的一部分是,带有事件处理程序的元素被innerHTML删除了,那么原来的元素的事件处理程序极有可能没有被当作垃圾回收
var myBtn=document.getElementById("myBtn"); myBtn.onclick=function(){
document.getElementById("myDiv").innerHTML="Processing..."
}
要想被回收,可以手动回收
var myBtn=document.getElementById("myBtn");
myBtn.onclick=function(){
btn.onclick=null;
document.getElementById("myDiv").innerHTML="Processing..."
}
`
- 第二种是卸载页面的时候,存在没有彻底清理干净的页面,一般来说,最好在卸载页面之前,先通过onunload事件处理程序移除所有的事件处理程序
六、模拟事件
6.1、DOM中的事件模拟
步骤如下:
- 使用document.createEvent()来创建event对象,这个方法接受一个参数,即表示要创建的事件类型的字符串。在DOM2中,这些字符串都使用英文复数的形式,而在DOM3中都变成了单数,如下:
UIEvents:一般化的UI事件
MouseEvents:一般化的鼠标事件
MutationEvents:一般化的DOM变动事件
HTMLEvents:一般化地HTML事件 - 创建了event对象之后,还要对其进行初始化。每种类型地event对象都有一个特殊的方法,为它传入适当地数据就可以初始化该event对象。
- 使用dispatchEvent()方法来出发事件
6.1.1、模拟鼠标事件
方法是为createEvent()传入字符串“MouseEvents”
返回地对象有一个名为initMouseEvent()的方法,用于指定与鼠标事件有关的信息(即初始化)
var btn = document.getElementById("myBtn");
var btn2 = document.getElementById("myBtn2");
EventUtil.addHandler(btn, "click", function(event){
console.log("Clicked!");
console.log(event.screenX); //100
});
EventUtil.addHandler(btn2, "click", function(event){ //create event object
var event = document.createEvent("MouseEvents"); //initialize the event object
event.initMouseEvent("click", true, true, document.defaultView, 0, 100, 0, 0, 0, false, false, false, false, 0, btn2); //fire the event
btn.dispatchEvent(event);
});
6.1.2、模拟键盘事件
DOM3级规定,调用createEvent()并传入KeyboardEvent就可以创建一个键盘事件。返回的对象中包含一个initKeyEvent()方法
由于DOM3级中不提倡使用keypress,故只能用来模拟keyup和keydown
另外。Firefox的模拟方法也不一样
所以兼容性写法为(该方法已经过时)
var btn=document.getElementById("myBtn");
var textbox=document.getElementById("myTextbox");
EventUtil.addHandler(textbox, "keydown", function(event){
console.log(event.type);
console.log(event.keyCode);
});
EventUtil.addHandler(btn,"click",function(event){
//以DOM3级创建事件对象
if (document.implementation.hasFeature("KeyboardEvents", "3.0")){
event = document.createEvent("KeyboardEvents");
event.initKeyboardEvent("keydown", true, true,document.defaultView,"a", 0, "Shift",0);
}else{
//Firefox
try{
event=document.createEvent("KeyEvents")
event.initKeyEvent("keydown", true, true,
document.defaultView, false, false,false,false,65,65)
//其他浏览器
}catch{
event=document.createEvent("Events")
event.initEvent("keydown", true, true);
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)
})
mdn新方法
鼠标事件:MouseEvent()
触发事件为dispatchEvent(event);
<p><label><input type="checkbox" id="checkbox"> Checked</label>
<p><button id="button">Click me</button>
function simulateClick() {
var evt = new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window,
});
var cb = document.getElementById("checkbox"); //element to click on
var canceled = !cb.dispatchEvent(evt);
if(canceled) {
// A handler called preventDefault
alert("canceled");
} else {
// None of the handlers called preventDefault
alert("not canceled");
}
}
document.getElementById("button").addEventListener('click', simulateClick);
键盘事件
event = new KeyboardEvent(typeArg, KeyboardEventInit);
var btn = document.querySelector('.button');
document.addEventListener('keyup', function (event) {
console.log(String.fromCharCode(event.keyCode));
}, false);
var ev = new KeyboardEvent('keyup', {
keyCode: 65
});
document.dispatchEvent(ev);
6.1.5、模拟其他事件
变动事件和HTML事件
6.1.4、自定义DOM事件
// 添加一个适当的事件监听器
obj.addEventListener("cat", function(e) { process(e.detail) })
// 创建并分发事件
var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}})
obj.dispatchEvent(event)