BOM(浏览器对象模型)和DOM(文档对象模型)的事件之间的关系并不十分清晰
1、事件流
<!DOCTYPY html>
<html>
<head>
<title>test</title>
</head>
<body>
<div>xxx</div>
</body>
<html>
- 事件冒泡:从具体节点->文档(document),即div->body->html->document->window
- 事件捕获:从文档(document)->具体节点,即window->documet->html->body->div
DOM2级事件流包括三个阶段:事件捕获、处于目标阶段、事件冒泡,并规定捕获阶段不会涉及事件目标。浏览器都会捕获阶段触发事件对象上的事件。结果,有两个机会在目标对象上操作事件。
注:IE8及更早版本不支持DOM事件流
2、事件处理程序
html事件处理程序
<input type="button" value="click me" onclick="alert('clicked')" id="myBtn"/>
使用onclick属性将js代码作为值,其中js不能使用非转义的html语法字符,如&、<、>、""
DOM0级事件处理程序
var btn = document.getElementById('myBtn');
btn.onclick = function(){
alert(this === btn) //true
}
btn.onclick = null;//删除事件处理程序
在冒泡阶段调处理,一个元素只能添加一个。通常情况下事件监听函数如果返回一个值并且是false,则会阻止浏览器执行默认的动作
DOM2级事件处理程序
var btn = document.getElementById('myBtn');
function sayHi(){
alert('clecked')
}
btn.addEventListener('click', sayHi , false);
//删除事件处理程序
btn.removeEventListener('click', sayHi, false);
addEventListener的第三个参数true表示在捕获阶段调用事件处理程序,false表示在冒泡阶段调用事件处理程序。addEventListener添加事件,只能removeEventListener才能移除,并且参数一致,添加匿名函数无法移除。- 一个元素可以添加多个,按照添加顺序执行,先添加先执行
IE事件处理程序
var btn = document.getElementById('myBtn');
function sayHi(){
alert(this === window) //true
}
btn.attachEvent('onclick', sayHi);
//删除事件处理程序
btn.detachEvent('onclick', sayHi);
冒泡阶段处理事件处理程序。移除函数必须和添加是相同参数才能移除成功。一个元素可以添加多个,按照添加顺序反向执行,先添加后执行(栈的形式)。
跨浏览器的事件处理程序
var EventUtil = {
addHandler: function(el, type, handler){
if(el.addEventListener){
el.addEventListener('type', handler, false);
} else if(el.attachEvent){
el.attachEvent('on' + type, handler);
} else {
el['on' + type ] = handler;
}
},
removeHandler: function(el, type, handler){
if(el.removeEventListener){
el.removeEventListener('type', handler, false);
} else if(el.detaccEvent){
el.detachEvent('on' + type, handler);
} else {
el['on' + type ] = null;
}
}
}
var btn = document.getElementById('myBtn');
function sayHi(){
alert(123)
}
EventUtil.addHandler(btn, 'click', sayHi);
//删除事件处理程序
EventUtil.removeHandler(btn, 'click', sayHi);
注:DOM1级的标准中并没有定义事件相关的内容,所以没有所谓的DOM1级事件模型
3、事件对象event
DOM中的事件对象
触发DOM上的某个元素时,就发生一个事件event对象,有如下属性和方法,都是只读
event.currentTarget //事件处理程序注册的那个元素=== this
event.target //实际事件触发的元素,即事件的实际目标
event.cancelable //是否可以取消事件的默认行为
event.type //事件类型,如'click'
event.eventPhase //确定事件当前正位于事件流的哪个阶段,值为1、2、3
event.trusted // false自定义, true浏览器生成的
event.preventDefault() //取消默认行为
event.stopPropagation() //阻止事件捕获和冒泡
event.stopImmediaPropagation()//阻止事件捕获和冒泡,阻止任何事件处理程序调用
document.body.onclick = function(event){
console.log(event.currentTarget === document.body) //true
console.log(this === event.body) //true
console.log(event.target === document.getElementById('myBtn')) // false
}
按下按钮时,冒泡到body,currentTarget和this等于body,target等于按钮元素
IE中的事件对象
event.cancelBubble //读/写,默认值false,不取消冒泡,类似stopPropagation()
event.retrunValue //读/写,默认值true,不取消事件的默认行为,类型preventDefault()
event.srcElement //读,事件的目标,类似target
var btn = document.getElementById('myBtn');
//event对象作为window对象的一个属性存在
btn.onclick = function(){
var event = window.event;
console,log(window.event.srcElement === this) //true
window.event.returnValue = false; //阻止默认行为
window.event.cancelBubble = true; //阻止冒泡
}
//
btn.attachEvent('onclick', function(event){
console.log(event.type) //click
console,log(event.srcElement === this) //false
});
跨浏览器的事件对象
var EventUtil = {
getEvent: function(event){
return event ? event : window.event;
},
getTarget: function(event){
return event.target || event.srcElement;
},
preventdefault: function(event){
if(event.preventdefault){
event.preventdefault();
} else {
event.returnVlalue = false;
}
},
stopPropagation: function(event){
if(event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}
var btn = document.getElementById('myBtn');
function sayHi(event){
event = EventUtil.getEvent(event);
let target = EventUtil.getTarget(event);
EventUtil.preventdefault(event);
EventUtil.stopPropagation(event);
}
EventUtil.addHandler(btn, 'click', sayHi);
4、事件类型
- resize事件,ff只会在用户停止调整窗口大小时才会触发,其他浏览器会在浏览器窗口变化1像素时就触发,随着变化不断触发。
- blur和focus事件,不冒泡;focusin和focusout事件,会冒泡
- click鼠标点击或者按下回车键时触发
- mouseenter和mouseleave事件,不冒泡。mouseenter元素外部移到元素内部,移到后代元素上不会触发。
- mouseout和mouseover事件,会冒泡。mouseout,从一个元素移入另一个元素时触发,可能是这个元素的字元素,或者外部。mouseover首次移入另一个元素边界内时触发。
- mousewheel滚轮事件
mousedown->mouseup->click->mousedown->mouseup->click->dbclick
鼠标事件的坐标位置
event.clinetX //事件发生时,鼠标指针在视口中的水平坐标
event.clienY
event.pageX //事件发生时,鼠标指针在页面中的水平坐标
event.pageY
event.screenX //事件发生时,鼠标指针在屏幕中的水平坐标
event.screenY
注:IE8及更早版本不支持pageX和pageY,可以通过event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft)
鼠标事件的相关元素
var EventUnit = {
getRelatedTarget: function(event){
if(event.relateTarget){
return event.relateTarget;
} else if(event.toElement){ //IE8及更低版本
return event.toElement;
} else if(event.fromElement){ //IE8及更低版本
return event.fromElement;
} else {
return null;
}
}
}
event相关元素属性relatedTarget,针对mouseout和mouseover事件才有值,其他事件这个值为null。mouseover事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。mouseout事件主目标是失去光标的元素,而相关元素则是获得光标的元素。
鼠标按钮
event.button //0表示主鼠标,1表示中间鼠标(滚轮),2次鼠标按钮
注:IE8及以前版本,则0-7,含义也不同于DOM的button
鼠标滚轮事件
mousewheel事件的event对象包含一个特殊的wheelDelta属性,向上+120,向下-120
注:ff中DOMMouseScroll事件,event.detail,保存向上-3,向下+3
var EventUnit = {
getWheelDelta: function(event){
if(event.wheelDelta){
return client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta;
} else {
return -event.detail * 40;
}
}
}
键盘与文本输入
- keydown,按下任意键触发,按下不放会一直重复触发
- keypress,按下字符键触发。
- keyup,释放键盘
- textInput事件,event.data属性,用户输入的字符。只有编辑区域才能触发改事件
keydown和keyup事件的event对象有keyCode属性,键码。keypress事件的event对象有charCode属性,字符编码
html5事件
- contextmenu事件
- beforeunload事件,会在浏览器卸载页面之前触发
- DOMContentloaded事件,形成完整的DOM树之后就会触发。
- readystatechange事件,和document.readyState属性配合使用,取值loading、loaded、interactive(交互)、complete
- pageshow和pagehide事件,往返缓存bfcache,IE9及之前版本不支持。pageshow事件的event.persisted属性true,表示页面来自bfcache。
- hashChange事件,URL的参数列表发生变化时触发
触摸与手势事件
5、内存和性能
事件委托
事件委托是利用事件冒泡实现
<ul>
<li id="1">1</li>
<li id="2" >2</li>
<li id="3">3</li>
</ul>
传统方法会给每个li绑定一个事件,有3个事件处理程序。利用事件委托,可以只给ul绑定一个事件处理程序
移除事件处理程序
某个元素即将被移除,最好手工移除事件处理程序。如在操作innerHTML时。卸载页面时,通过onunload事件移除所有事件处理程序