JavaScript事件-事件冒泡,事件捕获,事件绑定与解绑,事件委托
一、事件
HTML 事件可以是浏览器行为,也可以是用户行为。
通常,在事件触发时 JavaScript 可以执行一些代码。
二、DOM事件三个阶段
当一个DOM事件被触发时,它不仅仅只是单纯地在本身对象上触发一次,而是会经历三个不同的阶段:
- 捕获阶段:先由文档的根节点document往事件触发对象,从外向内捕获事件对象;
- 目标阶段:到达目标事件位置(事发地),触发事件;
-
冒泡阶段:再从目标事件位置往文档的根节点方向回溯,从内向外冒泡事件对象。
冒泡型事件流
捕获型事件流
-
.W3C标准模型
"DOM2事件"规范要求的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段
W3C标准则采用捕获+冒泡
W3C事件模型中发生的任何事件,先从其祖先元素window开始一路向下捕获, 直到达到目标元素,其后再次从目标元素开始冒泡。
而作为开发者, 可以决定事件处理器是注册在捕获或者是冒泡阶段。如果addEventListener的最后一个参数是true, 那么处理函数将在捕获阶段被触发; 否则(false), 会在冒泡阶段被触发!默认false!
三、事件绑定与解绑
总结:
当使用on或者addEventListener绑定事件的时候,如果绑定同一个事件同一个方法,只会执行一次,但是attachEvent会多次绑定。在jquery中,无论是on添加事件,还是直接加事件函数,同一个事件都可以多次绑定
1.直接在HTML中事件处理(不推荐)
<input type="button" value="showClick" onclick="showClick()" />
2.直接在dom对象上注册事件名称,就是DOM0写法,所有浏览器支持
如果handle是同一个方法,只执行一次。
//事件绑定
document.getElementById("box").onclick = handle;/
document.getElementById("box")["onmousemover"] = handle;
//事件解绑
document.getElementById("box")["onmousemover"] = null;
//阻止默认事件(默认事件行为:href=""链接,submit表单提交等)
document.getElementById("box").onclick = function() {
…… //你的代码
return false; //通过返回false值阻止默认事件行为
};
注释:
return false 的含义不是阻止事件继续向顶层元素传播,而是阻止浏览器对事件的默认处理。
return false 只在当前函数有效,不会影响其他外部函数的执行。
总结:
retrun true; 返回正确的处理结果。
return false;返回错误的处理结果,终止处理;阻止提交表单;阻止执行默认的行为。
return;把控制权返回给页面。
3.DOM2事件模型
- DOM2支持同一dom元素注册多个同种事件。
- DOM2新增了捕获和冒泡的概念。
(1)addEventListener(event.type, handle, boolean); IE8及以下不支持
事件类型没有on,第三个参数false,表示在事件第三阶段(冒泡)触发,true表示在事件第一阶段(捕获)触发。
如果handle是同一个方法,只执行一次。
var element=document.getElementById("box");
var handler=function(){
event.preventDefault( ); //阻止默认事件
}
//绑定事件
element.addEventListener('click', handler, false);
//解绑事件
element.removeEventListener('click', handle, false);
(2)attachEvent(event.type, handle ); IE特有,兼容IE8及以下,可添加多个事件处理程序,只支持冒泡阶段,并不属于DOM2
如果handle是同一个方法,绑定几次执行几次,这点和addEventListener不同。事件类型要加on,例如onclick而不是click
特别注意:
在 IE 中使用 attachEvent() 与DOM0和DOM2addEventListener有一主要区别:事件处理程序的作用域。在这些方法中,事件处理程序会在其所属元素的作用域内运行;在使用 attachEvent() 方法的情况下,事件处理程序会在全局作用域中运行,因此 this 等于 window。
var element=document.getElementById("box");
var handler=function(){
event.returnValue = false; //阻止默认事件
}
/绑定事件
element.attachEvent('onclick', handler);
//解绑事件,参数和绑定一样
element.detachEvent("onclick", handler);
(3)封装事件绑定与解绑函数,兼容浏览器
// 事件绑定
function addEvent(element, eType, handler, bol) {
var boll = bol | false;
if(element.addEventListener){ //如果支持addEventListener
element.addEventListener(eType, handler, boll );
}else if(element.attachEvent){ //如果支持attachEvent
element.attachEvent("on"+eType, handler);
}else{ //否则使用兼容的onclick绑定
element["on"+eType] = handle;
}
}
// 事件解绑
function removeEvent(element, eType, handler, bol) {
var boll = bol | false;
if(element.addEventListener){
element.removeEventListener(eType, handler, boll );
}else if(element.attachEvent){
element.detachEvent("on"+eType, handler);
}else{
element["on"+eType] = null;
}
}
我们可以将两个函数写成函数的方法模式
//用事件冒泡方式,如果想兼容事件捕获只需要添加个bool参数
var EventUtil = {
addEvent: function(element,type,handler) {
if (element.addEventListener) {
element.addEventListener(type,handler,false);
}
else if (element.attachEvent) {
element.attachEvent('on'+type,handler);
}
else {
element['on'+type] = handler;
}
},
removeEvent: function(element,type,handler) {
if (element.removeEventListener)
{
element.removeEventListener(type,handler,false);
}
else if(element.detachEvent) {
element.detachEvent('on' +type,handler);
}
else {
element['on'+type] = null;
}
}
}
//使用形式
var box= document.getElementById("box");
var handler = function(){
console.log(111);
};
EventUtil.addEvent(box, "click", handler);
EventUtil.removeEvent(box, "click", handler);
四、事件冒泡–主要在于理解透彻,才能在实际中运用
- 从事件源,自下而上的过程中,阻止向上冒泡,即阻止触发上级元素的事件触发
- 也可以理解为,上级元素做事件触发时,在此事件源上无效
<div id="box">
<span id="spa">子子元素</span>
</div>
document.getElementById('spa').addEventListener('click',function(e){
console.log('spa')
var e = event||window.event;
e.stopPropagation()
//在子级及以下的一个元素上进行阻止冒泡,在这块区域不会触发父级及以上的相同事件
})
document.getElementById('box').addEventListener('click',function(){
console.log('box')
})
- 在vue中,为了防止click事件冒泡,防止点击子级部分触发父级事件,会在子级直接加一个@click.stop
阻止事件冒泡的方法:
function stopPropagation(event){
event=window.event||event;
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=true;
}
}
五、事件委托
一般有两种情况会用到事件委托
- 新增的子元素是没有事件的,说明添加子节点的时候,事件没有一起添加进去
- 子元素太多,一个个循环遍历添加事件耗费性能
使用事件委托的原理:
- 利用事件冒泡的特性,将里层的事件委托给外层事件,根据event对象的属性进行事件委托,改善性能。
- 使用事件委托能够避免对特定的每个节点添加事件监听器;事件监听器是被添加到它们的父元素上。
- 事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。
<ul id="J_List">
<li><div class="id">1</div></li>
<li><div class="id">2</div></li>
<li><div class="user-delete">3</div></li>
</ul>
var lis = document.querySelector('#J_List');
lis.addEventListener('click', function(e){
stopPropagation(e)
var target = e.target || e.srcElement;
//在这里处理子元素的事件
if (target && target.className.toLowerCase()==='user-delete') {
target.parentNode.parentNode.removeChild(target.parentNode);
}
})
在jquery中经常用到的,即事件委托
$('body').on('click','.box1',function(){})
六、最后给大家送上一个跨浏览器的事件对象
var EventUtil={
getEvent:function(event){
return event||window.event;
},
getTarget:function(event){
return event.target||event.srcElement;
},
preventDefault:function(){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue=false;
}
},
stopPropagation:function(){
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=true;
}
},
addEvent:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else if(element.attachEvent){
element["e"+type]=function(){
handler.call(element)
}
element.attachEvent("on"+type,element["e"+type]);
}else{
element["on"+type]=handler;
}
},
removeEvent:function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){
element.detachEvent("on"+type,element["e"+type]);
element["e"+type]=null;
}else{
element["on"+type]=null;
}
}
};