欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

什么是事件冒泡和事件捕获、事件委托,他们的作用

程序员文章站 2024-03-05 14:39:48
...

事件模型

JavaScript事件使得网页具备互动和交互性,我们应该对其深入了解以便开发工作,在各式各样的浏览器中,JavaScript事件模型主要分为3种:原始事件模型、DOM2事件模型、IE事件模型。

一.原始事件模型(DOM0级)

这是一种被所有浏览器都支持的事件模型,对于原始事件而言,没有事件流,事件一旦发生将马上进行处理,有两种方式可以实现原始事件:

(1)在html代码中直接指定属性值:

(2)在js代码中为 document.getElementsById(“demo”).onclick = doSomeTing()

优点:所有浏览器都兼容

缺点:1)逻辑与显示没有分离;2)相同事件的监听函数只能绑定一个,后绑定的会覆盖掉前面的,如:a.onclick = func1; a.onclick = func2;将只会执行func2中的内容。3)无法通过事件的冒泡、委托等机制(后面会讲到)完成更多事情。

因为这些缺点,虽然原始事件类型兼容所有浏览器,但仍不推荐使用。

二.DOM2事件模型

此模型是W3C制定的标准模型,现代浏览器(IE6~8除外)都已经遵循这个规范。W3C制定的事件模型中,一次事件的发生包含三个过程:

(1).事件捕获阶段,(2).事件目标阶段,(3).事件冒泡阶段。如下图所示

什么是事件冒泡和事件捕获、事件委托,他们的作用

事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。

事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。

事件冒泡:当某个元素的某类型事件被触发时(如onclick),它父级的同类型事件也会被触发,它的父级的父级同类型事件也会被触发,以此类推,一直触发到根元素。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。

IE 5.5: div -> body -> document

IE 6.0: div -> body -> html -> document

Mozilla 1.0: div -> body -> html -> document -> window

所有的事件类型都会经历事件捕获但是只有部分事件会经历事件冒泡阶段,例如submit事件就不会被冒泡。

事件的传播是可以阻止的:

  • 在W3c中,非 IE 使用stopPropagation() 方法

    • 即:事件对象.stopPropagation()
  • 在IE下使用cancelBubble=true 方法

    • 即:事件对象.cancelBubble=true
  • 兼容性写法封装:

    • function stopEvent(e){
      	if(e.stopPropagation){
      		e.stopPropagation
      	}else{
      		e.cancelBubble=true
      	}
      }
      

    标准的事件监听器该如何绑定?

监听器的增加:addEventListener(“eventType”,“handler”,“true|false”);

监听器的解除: removeEventListner(“eventType”,“handler”,“true!false”);

参数 作用
eventType 事件类型、如 click、mousedown
handler 处理的函数
true/false 是否在捕获阶段(true为捕获、false为冒泡)

三.IE事件模型

监听器的增加: attachEvent( “eventType”,“handler”)

监听器的解除: detachEvent(“eventType”,“handler” )

参数 作用
eventType 事件类型、如 onclick、onmousedown(注意加了on)
handler 处理的函数

IE的事件模型已经可以解决原始模型的三个缺点,但其自己的缺点就是兼容性,只有IE系列浏览器才可以这样写。

以上就是3种事件模型,在我们写代码的时候,为了兼容ie,通常使用以下写法:

var demo = document.getElementById('demo');
if(demo.attachEvent){
    demo.attachEvent('onclick',func);
}else{
    demo.addEventListener('click',func,false);
}

总结:

DOM0 DOM2 IE
事件名 事件名以“on”开头 事件名前没有“on” 事件名以“on”开头
冒泡 支持 支持,最后属性设为false 支持
捕获 不支持 支持,最后属性设为true 不支持
阻止事件流 event.stopPropagation() event.stopPropagation() event.cancelBubble=true
多处理程序注册 不支持 支持 支持,执行顺序与注册顺序相反
删除事件绑定 设置为空字符串或null 用处理程序名删除,匿名绑定不能删除 用处理程序名删除,匿名绑定不能删除
兼容性 全兼容 非IE IE

四、事件类型

1、鼠标相关的事件

​ click: 当用户单击鼠标按钮或按下回车键时触发
​ dblclick: 当用户双击主鼠标按钮时触发。
​ mousedown: 当用户按下了鼠标还未弹起时触发。
​ mouseup: 当用户释放鼠标按钮时触发。

​ mouseover: 当鼠标移到某个元素上方时触发,

​ mouseout: 当鼠标移出某个元素上方时触发,

​ mouseenter:当鼠标移到某个元素上方时触发,

​ mouseleave:当鼠标移出某个元素上方时触发,

​ mousemove: 当鼠标指针在元素上移动时触发。

​ 注:

mouseover和mouseenter的区别:

mouseover,mouseout:当从父元素进入到子元素区域时,会触发父元素的离开事件(mouseout),同时又触发了mouseover事件

mouseenter,mouseleave:当从父元素进入到子元素区域时,不会触发父元素的离开事件(mouseleave)

2、键盘相关的事件

​ keydown: 当用户按下键盘上任意键触发,如果按住不放,会重复触发(不停地触发)。

​ keyup: 当用户抬起了(松开了)键盘上的键,触发

​ keypress: 当用户按下键盘上的字符键触发,如果按住不放,会重复触发。

3、window对象的相关事件:

load: 当页面完全加载后在 window 上面触发,或当框架集加载完毕后在框架集上触发。
unload:当页面完全卸载后在 window 上面触发,或当框架集卸载后在框架集上触发。 (用的非常非常少)
scroll: 当用户滚动带滚动条的元素时触发。
resize: 当窗口或框架的大小变化时在 window 或框架上触发。

4、表单相关的事件

blur: 当页面或元素失去焦点时在 window 及相关元素上触发。

focus: 当页面或者元素获得焦点时在 window 及相关元素上面触发。

select: 当用户选择文本框(input 或 textarea)中的一个或多个字符触发。

change:当下拉框的选项发生改变,或者当文本框(input 或 textarea)内容改变且失去焦点后(并且内容有改变)触发。

submit: 当用户点击提交按钮在元素上触发。

reset: 当用户点击重置按钮在元素上触发。

五、事件对象

1、事件对象

​ 事件对象 就是保存着事件相关信息的对象。当事件发生时,会自动产生事件对象(不需要new),事件对象中包含着:事件源(发生事件的dom元素),点击是鼠标的哪个键,鼠标的当前位置(事件发生的位置)等等信息。

2、事件源:

​ 发生事件的DOM元素(按钮,文本框,div,下拉框)。在事件处理函数里可以用this表示。

3、事件对象的获取

​ 事件对象是在事件发生后才有的。

​ dom对象.onclick = function(event){

​ //event参数就是事件对象,事件对象必须写在事件处理函数里。

​ 为了考虑到低版本的IE浏览器,需要兼容性写法↓

var e = event || window.event;

​ }

六、事件对象的属性

1、button: 记录着鼠标按下的是哪个键:

​ 0:左键

​ 1:中键(滚轮)

​ 2:右键

2、鼠标位置相关的属性:

clientX clientY 事件距离可视区域左边上边的X,Y坐标
pageX pageY 原点位置,整个页面的左上角(带滚动高度)
screenX screenY(用得少了) 事件距离整个屏幕左边上边的X,Y坐标

3、键盘相关的属性:

keyCode:保存着用户的按键编码;兼容性写法 var code = 事件对象.which || 事件对象.keyCode

ctrlKey:是否按照ctrl键,true:按下了;false:没有按下

shiftKey:是否按照shift键,true:按下了;false:没有按下

altKey:是否按照alt键,true:按下了;false:没有按下

metaKey:是否按照window键,true:按下了;false:没有按下

七、事件对象的常用属性

事件对象.target :发生事件的事件源

事件对象.srcElement: 是指向触发事件的元素,它是什么就有什么样的属性

事件对象.currentTarget: 是当前的事件源,会随事件流的流动(捕获、冒泡)而发生变化。

event.target与event.currentTarget有什么不同?

target在事件流的目标阶段;currentTarget在事件流的捕获,目标及冒泡阶段。只有当事件流处在目标阶段的时候,两个的指向才是一样的, 而当处于捕获和冒泡阶段的时候,target指向被单击的对象而currentTarget指向当前事件活动的对象(一般为父级)。

八、阻止浏览器的默认行为

1、浏览器的默认行为

​ 浏览器的默认行为就是不用写任何代码,浏览器给网页上增加的默认效果,如:任何网页都有右键菜单

2、如何阻止

​ 非IE:

​ 事件对象的方法:preventDefault();

​ IE:

​ 事件对象的属性:returnValue

​ 第三种做法:在事件处理函数里,直接使用return false就行。

​ 如下是兼容性写法:

        	 if (event.preventDefault) {  
​	            event.preventDefault();  
​              } else {  
​                 event.returnValue = false;  
​             } 


举个例子,阻止鼠标右键的菜单功能

document.oncontextmenu = function(event){
    // 阻止右键菜单
    var e =event || window.event;
    e.preventDefault(); //这句话阻止了事件继续执行,所以,浏览器的默认行为没了
}

九、事件委托

1、事件委托的概念:

事件委托 也叫事件代理,在 传统的事件处理中,我们为每一个需要触发事件的元素添加事件处理器,但是这种方法将可能会导致内存泄露或者性能下降(特别是通过ajax获取数据后重复绑定事件,总之,越频繁风险越大)。事件代理在js中是一个非常有用但对初学者稍难理解的功能,我们将事件处理器绑定到一个父级元素上,避免了频繁的绑定多个子级元素,依靠事件冒泡机制与事件捕获机制,子级元素的事件将委托给父级元素。

2、事件委托的原理:

​ 利用事件冒泡,和事件对象的target属性完成

3、优点:

1) 针对新增加的子元素,事件依然有效
2 )最终编译后的代码量小。

4、实例

假设有一个列表, 列表中的每一个li 都需要绑定某个事件处理函数 ,做法如下代码。↓

一、用传统的做法给每一个li绑定事件

<body>
    <ul id="box">
        <li>第1个li</li>
        <li>第2个li</li>
        <li>第3个li</li>
        <li>第4个li</li>
    </ul>
</body>

<script>
    var oLis = document.getElementById("box").children;
    for(var i=0;i<oLis.length;i++){
        oLis[i].onclick = function(event){
            var e = event || window.event;
            console.log(e.target.innerHTML);
        }
    }
</script>

二、用事件委托的方式做法给每一个li绑定事件(实现批量给li绑定事件的功能)

给li的父元素ul绑定onclick事件,当点击li时,会冒泡到父元素上

    var oUl = document.getElementById("box");
    // 触发ul的onclick事件有两种情况:
    // 1、点击li会冒泡上去
    // 2、点击ul本身也会触发
    oUl.onclick = function (event) {
        var e = event || window.event;
        // tagName是标签名
        // 如果点击的是ul,那么不执行下面的代码
        if (e.target.tagName.toLowerCase() == "li") {
            // target是真正的事件源
            console.log(e.target.innerHTML);
        }
    }

事件委托其实很好理解,举个例子:

有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。

别的参考文章:https://www.cnblogs.com/leftJS/p/10948138.html