本文原连接:http://www.javashuo.com/article/p-ghctezii-k.htmljavascript
https://www.jb51.net/article/139997.htmhtml
事件模型
JavaScript事件使得网页具有互动和交互性,咱们应该对其深刻了解以便开发工做,在各式各样的浏览器中,JavaScript事件模型主要分为3种:原始事件模型、DOM2事件模型、IE事件模型。java
1.原始事件模型(DOM0级)
这是一种被全部浏览器都支持的事件模型,对于原始事件而言,没有事件流,事件一旦发生将立刻进行处理,有两种方式能够实现原始事件:node
(1)在html代码中直接指定属性值:<button id="demo" type="button" onclick="doSomeTing()" /> web
(2)在js代码中为 document.getElementsById("demo").onclick = doSomeTing()ajax
优势:全部浏览器都兼容chrome
缺点:1)逻辑与显示没有分离;2)相同事件的监听函数只能绑定一个,后绑定的会覆盖掉前面的,如:a.onclick = func1; a.onclick = func2;将只会执行func2中的内容。3)没法经过事件的冒泡、委托等机制(后面会讲到)完成更多事情。浏览器
由于这些缺点,虽然原始事件类型兼容全部浏览器,但仍不推荐使用。函数
2.DOM2事件模型
此模型是W3C制定的标准模型,现代浏览器(IE6~8除外)都已经遵循这个规范。W3C制定的事件模型中,一次事件的发生包含三个过程:工具
(1).事件捕获阶段,(2).事件目标阶段,(3).事件冒泡阶段。以下图所示
事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程当中,事件相应的监听函数是不会被触发的。
事件目标:当到达目标元素以后,执行目标元素该事件相应的处理函数。若是没有绑定监听函数,那就不执行。
事件冒泡:从目标元素开始,往顶层元素传播。途中若是有节点绑定了相应的事件处理函数,这些函数都会被一次触发。
全部的事件类型都会经历事件捕获可是只有部分事件会经历事件冒泡阶段,例如submit事件就不会被冒泡。
事件的传播是能够阻止的:
• 在W3c中,使用stopPropagation()方法
• 在IE下设置cancelBubble = true;
在捕获的过程当中stopPropagation();后,后面的冒泡过程就不会发生了。
标准的事件监听器该如何绑定:
addEventListener("eventType","handler","true|false");其中eventType指事件类型,注意不要加‘on’前缀,与IE下不一样。第二个参数是处理函数,第三个即用来指定是否在捕获阶段进行处理,通常设为false来与IE保持一致(默认设置),除非你有特殊的逻辑需求。监听器的解除也相似:removeEventListner("eventType","handler","true!false");
3.IE事件模型
IE不把该对象传入事件处理函数,因为在任意时刻只会存在一个事件,因此IE把它做为全局对象window的一个属性,为求证其真伪,使用IE8执行代码alert(window.event),结果弹出是null,说明该属性已经定义,只是值为null(与undefined不一样)。难道这个全局对象的属性是在监听函数里才加的?因而执行下面代码:
window.onload = function (){alert(window.event);}
setTimeout(function(){alert(window.event);},2000);
结果第一次弹出【object event】,两秒后弹出依然是null。因而可知IE是将event对象在处理函数中设为window的属性,一旦函数执行结束,便被置为null了。IE的事件模型只有两步,先执行元素的监听函数,而后事件沿着父节点一直冒泡到document。冒泡已经讲解过了,这里不重复。IE模型下的事件监听方式也挺独特,绑定监听函数的方法是:attachEvent( "eventType","handler"),其中evetType为事件的类型,如onclick,注意要加’on’。解除事件监听器的方法是 detachEvent("eventType","handler" )
IE的事件模型已经能够解决原始模型的三个缺点,但其本身的缺点就是兼容性,只有IE系列浏览器才能够这样写。
以上就是3种事件模型,在咱们写代码的时候,为了兼容ie,一般使用如下写法:
var demo = document.getElementById('demo');
if(demo.attachEvent){
demo.attachEvent('onclick',func);
}else{
demo.addEventListener('click',func,false);
}
event详解
上面已经讲解了3种事件模型,事件,大部分状况下指的是用户的鼠标动做和键盘动做,如点击、移动鼠标、按下某个键,为何说大部分呢,由于事件不仅仅只有这两部分,还有其余的例如document的load和unloaded。那么事件在浏览器中,到底包含哪些信息呢?
事件被封装成一个event对象,包含了该事件发生时的全部相关信息(event的属性)以及能够对事件进行的操做(event的方法)。
我为下图中的button绑定了一个点击事件,而后将event输出到控制台:
能够看到是一个MouseEvent对象,包含了一系列属性,如鼠标点击的位置等。那么敲击键盘时产生的event对象和它同样吗?看看就知道:
能够看到是一个KeyboardEvent对象,属性跟上面的也不太同样,如没有clientX/Y(敲键盘怎么能获取到鼠标的位置呢)。不论是MouseEvent仍是KeyboardEvent或是其余类型,都是继承自一个叫Event的类。
event对象经常使用属性、方法:
1. 事件定位相关属性
若是你细细看了MouseEvent对象里的属性,必定发现了有不少带X/Y的属性,它们都和事件的位置相关。具体包括:x/y、clientX/clientY、pageX/pageY、screenX/screenY、layerX/layerY、offsetX/offsetY 六对。为何有这么多X-Y啊?不要着急,做为一个web开发者,你应该了解各浏览器之间是有差别的,这些属性都有各自的意思:
x/y与clientX/clientY值同样,表示距浏览器可视区域(工具栏除外区域)左/上的距离;
pageX/pageY,距页面左/上的距离,它与clientX/clientY的区别是不随滚动条的位置变化;
screenX/screenY,距计算机显示器左/上的距离,拖动你的浏览器窗口位置能够看到变化;
layerX/layerY与offsetX/offsetY值同样,表示距有定位属性的父元素左/上的距离。
下面列出了各属性的浏览器支持状况。(+支持,-不支持)
offsetX/offsetY | W3C- | IE+ | Firefox- | Opera+ | Safari+ | chrome+ |
x/y | W3C- | IE+ | Firefox- | Opera+ | Safari+ | chrome+ |
layerX/layerY | W3C- | IE- | Firefox+ | Opera- | Safari+ | chrome+ |
pageX/pageY | W3C- | IE+- | Firefox+ | Opera+ | Safari+ | chrome+ |
clientX/clientY | W3C+ | IE+ | Firefox+ | Opera+ | Safari+ | chrome+ |
screenX/screenY | W3C+ | IE+ | Firefox+ | Opera+ | Safari+ | chrome+ |
注意:该表摘自其余文章,我未作所有验证,可是最新版本的现代浏览器,这些属性貌似是都支持了,为了更好的兼容性,一般选择W3C支持的属性。
2.其余经常使用属性
target:发生事件的节点;
currentTarget:当前正在处理的事件的节点,在事件捕获或冒泡阶段;
timeStamp:事件发生的时间,时间戳。
bubbles:事件是否冒泡。
cancelable:事件是否能够用preventDefault()方法来取消默认的动做;
keyCode:按下的键的值;
3. event对象的方法
event. preventDefault()//阻止元素默认的行为,如连接的跳转、表单的提交;
event. stopPropagation()//阻止事件冒泡
event.initEvent()//初始化新事件对象的属性,自定义事件会用,不经常使用
event. stopImmediatePropagation()//能够阻止掉同一事件的其余优先级较低的侦听器的处理(这货表示没用过,优先级就不说明了,谷歌或者问度娘吧。)
event.target与event.currentTarget他们有什么不一样?
target在事件流的目标阶段;currentTarget在事件流的捕获,目标及冒泡阶段。只有当事件流处在目标阶段的时候,两个的指向才是同样的, 而当处于捕获和冒泡阶段的时候,target指向被单击的对象而currentTarget指向当前事件活动的对象(通常为父级)。
事件触发器
前面提到的事件都是依靠用户或者浏览器自带事件去触发的,好比click是用户点击事件目标触发,load是指定元素已载入的时候浏览器的行为事件,等等,若是只有在这些条件下才能触发事件,那么咱们的自定义事件如何触发呢?
事件触发器就是用来触发某个元素下的某个事件,固然也能够用来触发自定义事件IE下fireEvent方法,现代浏览器(chrome,firefox等)有dispatchEvent方法。
这里先介绍下自定义事件(事件模拟):
自定义事件
想要实现一个自定义事件,须要通过下面几步:
1.createEvent(eventType)
事件被封装成一个event对象,这在上面已经说过了,咱们想要自定义一个事件,js中有这么一个方法createEvent(eventType),见名知义,显然是用于“创造”一个事件的,没错,要想自定义事件,首先,咱们得“创造”一个事件。
参数:eventType 共5种类型:
Events :包括全部的事件.
HTMLEvents:包括 'abort', 'blur', 'change', 'error', 'focus', 'load', 'reset', 'resize', 'scroll', 'select',
'submit', 'unload'. 事件
UIEevents :包括 'DOMActivate', 'DOMFocusIn', 'DOMFocusOut', 'keydown', 'keypress', 'keyup'.
间接包含 MouseEvents.
MouseEvents:包括 'click', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup'.
MutationEvents:包括 'DOMAttrModified', 'DOMNodeInserted', 'DOMNodeRemoved',
'DOMCharacterDataModified', 'DOMNodeInsertedIntoDocument',
'DOMNodeRemovedFromDocument', 'DOMSubtreeModified'.
2. 在createEvent后必须初始化,为你们介绍5种对应的初始化方法
HTMLEvents 和 通用 Events:
initEvent( 'type', bubbles, cancelable )
UIEvents:
initUIEvent( 'type', bubbles, cancelable, windowObject, detail )
MouseEvents:
initMouseEvent( 'type', bubbles, cancelable, windowObject, detail, screenX, screenY,
clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget )
MutationEvents :
initMutationEvent( 'type', bubbles, cancelable, relatedNode, prevValue, newValue,
attrName, attrChange )
这里重点介绍MouseEvents(鼠标事件模拟):
鼠标事件能够经过建立一个鼠标事件对象来模拟(mouse event object),而且授予他一些相关信息,建立一个鼠标事件经过传给createEvent()方法一个字符串“MouseEvents”,来建立鼠标事件对象,以后经过iniMouseEvent()方法来初始化返回的事件对象,iniMouseEvent()方法接受15参数,参数以下:
type string类型 :要触发的事件类型,例如‘click’。
bubbles Boolean类型:表示事件是否应该冒泡,针对鼠标事件模拟,该值应该被设置为true。
cancelable bool类型:表示该事件是否可以被取消,针对鼠标事件模拟,该值应该被设置为true。
view 抽象视图:事件授予的视图,这个值几乎全是document.defaultView.
detail int类型:附加的事件信息这个初始化时通常应该默认为0。
screenX int类型 : 事件距离屏幕左边的X坐标
screenY int类型 : 事件距离屏幕上边的y坐标
clientX int类型 : 事件距离可视区域左边的X坐标
clientY int类型 : 事件距离可视区域上边的y坐标
ctrlKey Boolean类型 : 表明ctrol键是否被按下,默认为false。
altKey Boolean类型 : 表明alt键是否被按下,默认为false。
shiftKey Boolean类型 : 表明shif键是否被按下,默认为false。
metaKey Boolean类型: 表明meta key 是否被按下,默认是false。
button int类型: 表示被按下的鼠标键,默认是零.
relatedTarget (object) : 事件的关联对象.只有在模拟mouseover 和 mouseout时用到。
若是你想要了解更多事件模拟参数详解,请查看这篇文章,http://www.cnblogs.com/MrBackKom/archive/2012/06/26/2564501.html。或者查看《javascript高级程序设计》的模拟事件章节
3. 在初始化完成后就能够随时触发须要的事件了,为你们介绍targetObj.dispatchEvent(event)使targetObj对象的event事件触发。(IE上请用fireEvent方法)
4. 例子
//例子1 当即触发鼠标被按下事件
1
2
3
4
|
var
fireOnThis = document.getElementById(
'demo'
);
var
evObj = document.createEvent(
'MouseEvents'
);
evObj.initMouseEvent(
'click'
,
true
,
true
, window, 1, 12, 345, 7, 220,
false
,
false
,
true
,
false
, 0,
null
);
fireOnThis.dispatchEvent(evObj);
|
//例子2 考虑兼容性的一个鼠标移动事件
1
2
3
4
5
6
7
8
9
|
var
fireOnThis = document.getElementById(
'someID'
);
if
( document.createEvent ) {
var
evObj = document.createEvent(
'MouseEvents'
);
evObj.initEvent(
'mousemove'
,
true
,
false
);
fireOnThis.dispatchEvent(evObj);
}
else
if
( document.createEventObject )
{
fireOnThis.fireEvent(
'onmousemove'
);
}
|
事件代理
传统的事件处理中,咱们为每个须要触发事件的元素添加事件处理器,可是这种方法将可能会致使内存泄露或者性能降低(特别是经过ajax获取数据后重复绑定事件,总之,越频繁风险越大)。事件代理在js中是一个很是有用但对初学者稍难理解的功能,咱们将事件处理器绑定到一个父级元素上,避免了频繁的绑定多个子级元素,依靠事件冒泡机制与事件捕获机制,子级元素的事件将委托给父级元素。事件冒泡与捕获在上面事件模型中已经讲解过。
有了事件捕获和冒泡的认识后,下面举例说明事件代理:
假设咱们有一个列表,列表中的每个li和li中的span都须要绑定某个事件处理函数。以下代码:
1
2
3
4
5
6
7
8
|
<ul id=
"parent-ul"
>
<li><span>Item 1</span></li>
<li><span>Item 2</span></li>
<li><span>Item 3</span></li>
<li><span>Item 4</span></li>
<li><span>Item 5</span></li>
<li><span>Item 6</span></li>
</ul>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
~(
function
() {
var
ParentNode = document.querySelector(
"#parent-ul"
);
var
targetNodes = ParentNode.querySelectorAll(
"li"
);
var
spanNodes = ParentNode.querySelectorAll(
"span"
);
//绑定事件处理函数
for
(
var
i=0, l = targetNodes.length; i < l; i++){
addListenersToLi(targetNodes[i]);
}
for
(
var
i=0, l = spanNodes.length; i < l; i++){
addListenersToSpan(spanNodes[i]);
}
//事件处理函数
function
addListenersToLi(targetNode){
targetNode.onclick =
function
targetClick(e){
if
(e.target && e.target.nodeName.toUpperCase() ==
"LI"
) {
console.log(
"当你看见个人时候,LI点击事件已经生效!"
);
}
};
}
function
addListenersToSpan(targetNode){
targetNode.onclick =
function
targetClick(e){
if
(e.target && e.target.nodeName.toUpperCase() ==
"SPAN"
) {
console.log(
"当你看见个人时候,SPAN点击已经生效!"
);
}
};
}
})();
|
这里为li和span元素都添加了onclick事件处理函数,可是若是这些li和span有可能被删除或者新增,那么老是须要为新增的li、span元素从新绑定事件,这种写法使得咱们很苦恼,除了开头提到的问题外,增长了代码量并且代码看上去不太整洁了,那么使用事件代理会怎么样呢?以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
~(
function
() {
// 获取li的父节点,并为其添加一个click事件
document.getElementById(
"parent-ul"
).addEventListener(
"click"
,
function
(e) {
// 检查事件源e.targe是否为span
if
(e.target && e.target.nodeName.toUpperCase() ==
"SPAN"
) {
// 真正的处理过程在这里
console.log(
"当你看见个人时候,SPAN事件代理已经生效!"
);
}<br>
//检查事件源e.target是否为li
if
(e.target && e.target.nodeName.toUpperCase() ==
"LI"
) {
// 真正的处理过程在这里
console.log(
"当你看见个人时候,LI事件代理已经生效!"
);
}
});
})();
|
咱们改变了思路,为li、span的父级元素即id为parent-ul的ul元素添加了一click事件,当点击事件发生时,咱们能够经过e.target捕获事件目标,并经过e.target.nodeName.toUpperCase== "LI"来判断事件目标是否为li(span同理),如是那么执行相应的事件处理程序。使用这样的方式有利于解决前面提到的一些问题:
1.最直接的就是,代码更整洁了,并且可读性更强。
2.对于动态化的页面(如本例,li、span会新增和删除),不用频繁的绑定事件,减小了内存泄露的几率。
注意:不是全部的事件都能冒泡的。blur、focus、load和unload不能像其它事件同样冒泡。事实上blur和focus能够用事件捕获而非事件冒泡的方法得到(在IE以外的其它浏览器中)。
在管理鼠标事件的时候有些须要注意的地方。若是你的代码处理mousemove事件的话你赶上性能瓶颈的风险可就大了,由于mousemove事件触发很是频繁。而mouseout则由于其怪异的表现而变得很难用事件代理来管理。
jq事件代理:jq为提供了delegate()函数处理事件代理,这里很少介绍,我的在工做中使用on()函数解决一些事件代理问题(使用更方便),解决上诉例子的代码以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
~(
function
() {
$(
"#parent-ul"
).on(
"click"
,
"li,span"
,
function
(e) {
if
($(
this
)[0].nodeName==
"SPAN"
) {
// 真正的处理过程在这里
console.log(
"当你看见个人时候,SPAN事件代理已经生效!"
);
//这里要阻止冒泡,否则点击span时会触发li的事件
e.stopPropagation();
}
if
($(
this
)[0].nodeName==
"LI"
) {
// 真正的处理过程在这里
console.log(
"当你看见个人时候,LI事件代理已经生效!"
);
}
});
})();
|
若是你使用jq,推荐使用on()方法。