JavaScript事件——事件冒泡,事件捕获,事件绑定与解绑,
一.事件
事件是用户或浏览器自身执行的某种动作,这是我自己的理解。
二.事件流
事件流描述的是从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序。
1.两种事件流模型
冒泡型事件流:事件的传播从DOM树的叶子到根。【推荐】——event bubbling
捕获型事件流:事件的传播葱DOM树的根到叶子。 ——event capturing
2.W3C标准模型
W3C标准采用捕获+冒泡。两种事件流都会触发DOM的所有对象,从document对象开始,也在document对象结束。
W3C标准则取其折中方案. W3C事件模型中发生的任何事件, 先(从其祖先元素window)开始一路向下捕获, 直到达到目标元素, 其后再次从目标元素开始冒泡.
而你作为开发者, 可以决定事件处理器是注册在捕获或者是冒泡阶段. 如果addEventListener的最后一个参数是true, 那么处理函数将在捕获阶段被触发; 否则(false), 会在冒泡阶段被触发.
3.浏览器兼容问题
一些标准的浏览器中,如Chrome、Firefox等,支持这两种方式。而事实上,准确的说,是这两种方式的混合方式。
在低版本IE及Opera下,是不支持事件捕获的。而这个特点最明显体现在事件绑定函数上。
IE、Opera的事件绑定函数是attachEvent,而Chrome等浏览器则是addEventListener,而后者比前者的参数多了一个,这个参数是一个布尔值。这个布尔值由用户决定,用户若设为true,则该绑定事件以事件捕获的形式参与,若为false则以事件冒泡的形势参与。
三.事件绑定与解绑
1.直接在HTML中事件处理(不推荐)
<input type="button" value="showClick" onclick="showClick()" />
这种方法的有很多缺点:
(1)首先,存在一个时差问题。因为用户如果会在 HTML 元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。假如用户在页面解析事件处理函数之前就触发事件,就会引发错误。为此,很多HTML事件处理程序都会被封装在一个 try-catch 块中,这样错误不会浮出水面,因为在浏览器有机会处理错误之前,错误就被捕获了。如下面的例子所示:
<input type="button" value="showClick" onclick="try{showClick();}catch(ex){}">
(2)这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。不同 JavaScript 引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。
(3)HTML 与 JavaScript 代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML 代码和 JavaScript 代码。而这正是许多开发人员摒弃 HTML 事件处理程序,转而使用 JavaScript 指定事件处理程序的原因所在。
2. 直接在dom对象上注册事件名称,就是DOM0写法,所有浏览器支持
//事件绑定
document.getElementById("patty").onclick = function(e){};/
document.getElementById("patty")["onmousemover"] = function(e){};
//事件解绑
document.getElementById("patty")["onmousemover"] = null;
//阻止默认事件(默认事件行为:href=""链接,submit表单提交等)
document.getElementById("patty").onclick = function() {
…… //你的代码
return false; //通过返回false值阻止默认事件行为
};
在这里我觉得有必要解释两点
(1)事件绑定通过.和[]的两种方式访问js对象属性的方法,[]的形式主要是为了解决属性名不是合法的标识符,比如:object.123肯定报错,但是object["123"]就避免了这个问题,与此同时,[]的写法,更加灵活,用字符串表示属性名称,可以在运行时动态绑定事件。
(2)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("patty");
var handler=function(){ }
//绑定事件
element.addEventListener('click', handler, false);
//解绑事件
element.removeEventListener('click', handle, false);
//阻止默认事件
element.addEventListener("click", function(e){
var event = e || window.event;
……
event.preventDefault( ); //阻止默认事件
},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("patty");
var handler=function(){ }
/绑定事件
element.attachEvent('onclick', handler);
//解绑事件,参数和绑定一样
element.detachEvent("onclick", handler);
//阻止默认事件
element.attachEvent("onclick", function(e){
var event = e || window.event;
……
event.returnValue = false; //阻止默认事件
},false);
(3)封装事件绑定与解绑函数,兼容浏览器
// 事件绑定
function addEvent(element, eType, handler, bol) {
if(element.addEventListener){ //如果支持addEventListener
element.addEventListener(eType, handler bol);
}else if(element.attachEvent){ //如果支持attachEvent
element.attachEvent("on"+eType, handler);
}else{ //否则使用兼容的onclick绑定
element["on"+eType] = handle;
}
}
// 事件解绑
function removeEvent(element, eType, handler, bol) {
if(element.addEventListener){
element.removeEventListener(eType, handler, bol);
}else if(element.attachEvent){
element.detachEvent("on"+eType, handler);
}else{
element["on"+eType] = null;
}
}
作为一个追求完美的人,我们可以将两个函数写成函数的方法模式
//用事件冒泡方式,如果想兼容事件捕获只需要添加个bool参数
var EventUtil = {
addHandler: 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;
}
},
removeHandler: 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 patty= document.getElementById("patty");
var handler = function(){
console.log("Don't touch patty");
};
EventUtil.addHandler(patty, "click", handler);
EventUtil.removeHandler(patty, "click", handler);
四.终止事件冒泡
事件冒泡、事件捕获阻止:
event.stopPropagation( ); // 阻止事件的进一步传播,包括(冒泡,捕获),无参数 ,ie不支持
event.cancelBubble = true; // true 为阻止冒泡,也有浏览器不支持
方法1:利用event.stopPropagation( )和event.cancelBubble
var patty=document.getElementById("patty").addEventListener("click",function(event){
var event = event||window.event;
……
event.stopPropagation();
});
这里提供一个阻止冒泡兼容性写法
function stopPropagation(event){
event=window.event||event;
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=true;
}
}
//直接调用
document.getElementById("patty").addEventListener("click",function(event){
......
stopPropagation(event);
}
方法2:利用event.target和event.currentTarget,事件包含最初触发事件的节点引用 和 当前处理事件节点的引用,节点只处理自己触发的事件
document.getElementById("patty").addEventListener("click",function(event){
event=window.event||event;
//IE没有event.target,有event.srcElement
var target = event.target||event.srcElement;
if(target == event.currentTarget)
{
……
}
});
方法一缺点:为了实现点击特定的元素显示对应的信息,方法一要求每个元素的子元素也必须终止事件的冒泡传递,即跟别的元素功能上强关联,这样的方法会很脆弱。
方法二缺点:方法二为每一个元素都增加了事件监听处理函数,事件的处理逻辑都很相似,即都有判断 if(target == event.currentTarget),这样存在了很大的代码冗余,现在是三个元素还好,当有10几个,上百个又该怎么办呢?
改进(利用事件委托)
让某个父节点统一处理事件,通过判断事件的发生地(即事件产生的节点),然后做出相应的处理
这里我们先了解下事件委托的概念
事件委托:
利用事件冒泡的特性,将里层的事件委托给外层事件,根据event对象的属性进行事件委托,改善性能。使用事件委托能够避免对特定的每个节点添加事件监听器;事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。
window.onload = function() {
document.getElementById("box").addEventListener("click",eventPerformed,false);
}
function eventPerformed(event) {
var event = event||window.event;
var target = e.target||e.srcElement;
switch (target.id) {
case "div1":
alert("您好,我是div1。");
break;
case "div2":
alert("您好,我是div2。");
break;
}
}
五.最后给大家送上一个跨浏览器的事件对象
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;
}
},
addHandler: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;
}
},
removeHandler: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;
}
}
};
//使用
var patty=document.getElementById("patty");
var handler=function(event){
var event=EventUtil.getEvent(event);
var target=EventUtil.getTarget(event);
alert(target.id);
EventUtil.stopPropagation(event);
}
EventUtil.addHandler(patty,'click',handler);
好了,关于JS事件暂且先写这么多,欢迎大家指正博文中错误。谢谢观看!