事件模型、事件流(冒泡与捕获)、事件代理
本文原链接:https://www.cnblogs.com/hngdlxy143/p/9068282.html
https://www.jb51.net/article/139997.htm
事件模型
JavaScript事件使得网页具备互动和交互性,我们应该对其深入了解以便开发工作,在各式各样的浏览器中,JavaScript事件模型主要分为3种:原始事件模型、DOM2事件模型、IE事件模型。
1.原始事件模型(DOM0级)
这是一种被所有浏览器都支持的事件模型,对于原始事件而言,没有事件流,事件一旦发生将马上进行处理,有两种方式可以实现原始事件:
(1)在html代码中直接指定属性值:<button id="demo" type="button" οnclick="doSomeTing()" />
(2)在js代码中为 document.getElementsById("demo").onclick = doSomeTing()
优点:所有浏览器都兼容
缺点: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()方法。
转载于:https://www.cnblogs.com/leftJS/p/11068426.html
事件模型、事件流(冒泡与捕获)、事件代理相关推荐
- js中的DOM事件之冒泡和捕获事件详解
DOM中的事件是一个很中要的东西,它可以让用户和浏览器之间进行交互,以此来实现人机交互效果 DOM事件 DOM事件分为DOM0级事件和DOM2级事件.DOM0级其实不存在,我们把DOM最初的版本叫0级 ...
- js中冒泡和捕获/阻止冒泡和捕获事件
JavaScript中捕获/阻止捕获.冒泡/阻止冒泡 事件流描述的是从页面中接收事件的顺序.提出事件流概念的正是IE和Netscape,但是前者提出的是我们常用的事件冒泡流,而后者提出的是事件捕获流. ...
- js 的冒泡和捕获事件
事件冒泡和事件捕获 事件的冒泡和捕获是决定事件的执行顺序,如下有两个 div,同时绑定了 click 事件,哪个div 绑定的事件先执行呢? <div class="outer&quo ...
- vue 事件上加阻止冒泡 阻止默认事件
重点 vue事件修饰符 <!-- 阻止单击事件冒泡 --> <a v-on:click.stop="doThis"></a><!-- 提交 ...
- Event事件-1:addEventListener事件监听 / 事件冒泡事件捕获 / 事件委托 / preventDefault 阻止默认行为 / cancelBubble、stopPropa...
addEventListener 事件监听器 target.addEventListener(type, listener[, options|useCapture]) 添加事件监听 参数: ...
- JS 中的事件冒泡与捕获
本文来源:渔人 原文地址:http://yuren.space/blog/2016/10/16/事件冒泡与捕获/ 刚接触 JS 的那个时候,啥也不懂,只想着如何利用 Google.百度到的函数来解决实 ...
- 行为模型实例 php,JS中事件模型的实例详解
之前对事件模型还是比较清楚的,许多概念都清晰映射在脑海中.工作之后,一方面使用的 局限性,二是习惯于用框架中的各种事件监听方式,简单即方便,久而久之,事件的一些概念开 始淡出记忆中,就像我现在已经开始 ...
- JavaScript 事件模型 事件处理机制
这篇文章对于了解Javascript的事件处理机制非常好,将它全文转载于此,以备不时之需. 什么是事件? 事件(Event)是JavaScript应用跳动的心脏 ,也是把所有东西粘在一起的胶水.当我们 ...
- 标准事件模型和IE事件模型
前言:在上一篇博客中总结了0级DOM事件模型和2级DOM事件模型,打铁趁热就在谈谈标准事件模型和IE事件模型的区别吧. 一. 标准事件模型 在JavaScript中把标准事件模型的执行分为三个阶段,即 ...
最新文章
- vim编辑器操作命令大全-绝对全
- HLS视频协议第一弹--centos下面配置ffmpeg,segmenter以适应hls切片需要
- 75.事务是什么?特征?
- Java | 设计模式-适配器模式
- 牛腩新闻发布系统——初探CSS
- 基于内容的图像检索系统(合集)
- 如何使用Mac电脑内置的屏幕共享功能进行远程桌面协助?
- android 加载过程,Android View (2) View的加载过程
- 关于Android Gradle你需要知道这些(2)
- Dagger2的介绍和配置
- fw325r虚拟服务器,fw325r管理页面
- Php将mp3转wav,mp3转wav格式转换器 mp3怎么转换成wav格式
- 360视频简介及ERP投影
- 独立站卖家如何借势营销
- 谈谈大学两年的学习经历
- (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
- 洛谷 P4094 [HEOI2016/TJOI2016]字符串 后缀数组+二分+主席树
- Android 项目是如何编译成.apk的
- C++第三方日志库Pantheios
- 高德citycode和国家citycode编码转换
热门文章
- SpringBoot - 优雅的处理【长事务】
- Java Review - 并发编程_ArrayBlockingQueue原理源码剖析
- Spring Boot - 自动配置实例解读
- Spring-在IoC中装配Bean系列文章导读
- Kafka基本的概念
- fx5u模拟量如何读取_FX5U系列三菱产品 使用模拟量时的注意事项
- Kotlin的基本数值类型问题:是对象?还是基本数据类型?
- 改善前端优化的有用技巧
- 【新星计划】汽车纵向动力学模型
- 个人笔记本上win10+yolov3+python+tensorflow+keras训练自己的识别模型