JS事件:捕获与冒泡、事件处理程序、事件对象、跨浏览器、事件委托
amandakelake opened this issue · 1 comments
一、捕获与冒泡
事件流描述的是从页面中接收事件的顺序
IE 的事件流是事件冒泡流
而 Netscape Communicator 的事件流是事件捕获流
DOM2级事件规定的事件流包括三个阶段:
画重点(这部分内容可以先跳过,看完下面的内容再回头消化)
1、当处于目标阶段,没有捕获与冒泡之分,执行顺序会按照addEventListener
的添加顺序决定,先添加先执行
2、使用stopPropagation()
取消事件传播时,事件不会被传播给下一个节点,但是,同一节点上的其他listener还是会被执行
// list 的捕获
$list.addEventListener('click', (e) => {
console.log('list capturing');
e.stopPropagation();
}, true)
// list 捕获 2
$list.addEventListener('click', (e) => {
console.log('list capturing2');
}, true)
// list capturing
// list capturing2
如果想要同一层级的listener也不执行,可以使用stopImmediatePropagation()
3、preventDefault()
只是阻止默认行为,跟JS的事件传播一点关系都没有
4、一旦发起了preventDefault()
,在之后传递下去的事件里面也會有效果
二、事件处理程序
共有三种事件处理程序:DOM0、DOM2、IE
var btn = document.getElementById('btn');
btn.onClick = () => {
console.log('我是DOM0级事件处理程序');
}
btn.onClick = null;
btn.addEventListener('click', () => {
console.log('我是DOM2级事件处理程序');
}, false);
btn.removeEventListener('click', handler, false)
btn.attachEvent('onclick', () => {
console.log('我是IE事件处理程序')
})
btn.detachEvent('onclicn', handler);
画重点:
DOM2级的好处是可以添加多个事件处理程序;DOM0对每个事件只支持一个事件处理程序
通过DOM2添加的匿名函数无法移除,上面写的例子就移除不了,addEventListener
和removeEventListener
的handler
必须同名
作用域:DOM0的handler
会在所属元素的作用域内运行,IE的handler
会在全局作用域运行,this === window
触发顺序:添加多个事件时,DOM2会按照添加顺序执行,IE会以相反的顺序执行,请谨记
跨浏览器的事件处理程序
var EventUtil = {
// element是当前元素,可以通过getElementById(id)获取
// type 是事件类型,一般是click ,也有可能是鼠标、焦点、滚轮事件等等
// handle 事件处理函数
addHandler: (element, type, handler) => {
// 先检测是否存在DOM2级方法,再检测IE的方法,最后是DOM0级方法(一般不会到这)
if (element.addEventListener) {
// 第三个参数false表示冒泡阶段
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent(`on${type}`, handler)
} else {
element[`on${type}`] = handler;
}
},
removeHandler: (element, type, handler) => {
if (element.removeEventListener) {
// 第三个参数false表示冒泡阶段
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent(`on${type}`, handler)
} else {
element[`on${type}`] = null;
}
}
}
// 获取元素
var btn = document.getElementById('btn');
// 定义handler
var handler = function(e) {
console.log('我被点击了');
}
// 监听事件
EventUtil.addHandler(btn, 'click', handler);
// 移除事件监听
// EventUtil.removeHandler(button1, 'click', clickEvent);
三、事件对象
DOM0和DOM2的事件处理程序都会自动传入event
对象
IE中的event
对象取决于指定的事件处理程序的方法(上面说过)
IE的
handler
会在全局作用域运行,this === window
所以在IE中会有window.event
、event
两种情况
只有在事件处理程序期间,event
对象才会存在,一旦事件处理程序执行完成,event
对象就会被销毁
event对象里需要关心的几个属性
this、currentTarget、target
这三个属性跟冒泡和捕获有关
target
永远是被添加了事件的那个元素,this
和currentTarget
就不一定了(延伸思考:事件处理程序在父节点中的情况)
eventPhase
调用事件处理程序的阶段,有三个值
1:捕获阶段
2:处于目标
3:冒泡阶段
阻止默认preventDefault
与传播stopPropagation
preventDefault:比如链接被点击会导航到其href
指定的URL
,这个就是默认行为
stopPropagation:立即停止事件在DOM层次中的传播,包括捕获和冒泡事件
IE中的对象的对应属性
srcElement
=> target
returnValue
=> preventDefaukt()
cancelBubble
=> stopPropagation()
IE 不支持事件捕获,因而只能取消事件冒泡,但stopPropagation
可以同时取消事件捕获和冒泡
四、跨浏览器的事件对象
根据上面对不同类型的事件以及属性区分
var EventUtil = {
addHandler: (element, type, handler) => {},
removeHandler: (element, type, handler) => {},
// 获取event对象
getEvent: (event) => {
return event ? event : window.event
},
// 获取当前目标
getTarget: (event) => {
return event.target ? event.target : event.srcElement
},
// 阻止默认行为
preventDefault: (event) => {
if (event.preventDefault) {
event.preventDefault()
} else {
event.returnValue = false
}
},
// 停止传播事件
stopPropagation: (event) => {
if (event,stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
}
}
五、事件委托
这一小节在《高程》P403
我自认不能写的比它更精简更好,有些地方就直接搬过来了
事件委托用来解决事件处理程序过多的问题
页面结构如下
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
按照传统的做法,需要像下面这样为它们添加 3 个事 件处理程序。
var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");
EventUtil.addHandler(item1, "click", function(event){
location.href = "http://www.wrox.com";
});
EventUtil.addHandler(item2, "click", function(event){
document.title = "I changed the document's title";
});
EventUtil.addHandler(item3, "click", function(event){
alert("hi");
});
如果在一个复杂的 Web 应用程序中,对所有可单击的元素都采用这种方式,那么结果就会有数不 清的代码用于添加事件处理程序。此时,可以利用事件委托技术解决这个问题。使用事件委托,只需在 DOM 树中尽量最高的层次上添加一个事件处理程序,如下面的例子所示
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": 9 alert("hi");
break;
}
}
子节点的点击事件会冒泡到父节点,并被这个注册事件处理
最适合采用事件委托技术的事件包括 click
、mousedown
、mouseup
、keydown
、keyup
和 keypress
。 虽然 mouseover
和 mouseout
事件也冒泡,但要适当处理它们并不容易,而且经常需要计算元素的位置。
可以考虑为 document 对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件,需要跟踪的事件处理程序越少,移除它们就越容易(移除事件处理程序关乎内存和性能)。
只要是通过 onload 事件处理程序添加的东西,最后都要通过 onunload 事件处理程序将它们移除
在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中是事件冒泡的前提。
后记
感谢您耐心看到这里,希望有所收获!
如果不是很忙的话,麻烦右上角点个star⭐,举手之劳,却是对作者莫大的鼓励。
我在学习过程中喜欢做记录,分享的是自己在前端之路上的一些积累和思考,希望能跟大家一起交流与进步,更多文章请看【amandakelake的Github博客】
参考
DOM 的事件傳遞機制:捕獲與冒泡 | TechBridge 技術共筆部落格
What Is Event Bubbling in JavaScript? Event Propagation Explained
有两个建议:
1.当处于目标阶段,没有捕获与冒泡之分,执行顺序会按照addEventListener的添加顺序决定,先添加先执行 -------这句话现在在chrome的91版本已经不适用了,现在的91版本是按照先捕获在冒泡的顺序执行的,不是按代码顺序
2.使用stopPropagation()取消事件传播时------------stopPropagation()应该是取消了事件的冒泡,传播会让人误解
以上是个人的愚见,希望博主采纳