JavaScript事件绑定机制详解:从传统模型到跨浏览器兼容方案

作者:白露时间:2026-10-29 23:03:45浏览:91

本文深入解析JavaScript事件绑定的两种主要模型:传统脚本模型与现代W3C DOM2级模型。详细对比了window.onload覆盖问题、事件切换器实现以及this指向差异,并提供了针对IE8及以下浏览器的attachEvent兼容方案。内容涵盖事件捕获与冒泡、阻止默认行为、鼠标滚轮及卸载前事件等核心知识点,旨在帮助开发者构建稳定且兼容多浏览器的交互逻辑。

JavaScript事件绑定机制概述

事件绑定机制主要包含两种模式:传统事件绑定(涵盖内联模型与脚本模型)以及现代事件绑定(基于DOM2级标准)。现代绑定方式在传统基础上提供了更为强大且便捷的功能支持。

传统事件绑定的局限性

一、传统事件绑定存在的局限性

传统事件绑定中的内联模型因使用较少,此处不予讨论。重点分析脚本模型,其核心是将一个函数赋值给事件处理属性。传统绑定的典型示例如下:


window.onload=function(){
 var box=document.getElementById('box');
 box.onclick = function(){
  alert('Lee');
 };
};



问题一:事件处理函数被多次触发

当页面中存在多个JavaScript文件,且由不同开发者编写时,后续脚本对window.onload的赋值会覆盖前者的定义。例如:


window.onload=function(){
 alert('Lee');
};

window.onload=function(){
 alert('Mr.lee');
} 

最终结果仅执行了Mr.lee的提示,前一个事件被覆盖。

实际上存在解决该问题的方法,参考以下两种实现形式: a:


alert(window.onload);//一开始没有注册window.onload,那么就是null

window.onload=function(){
 alert('Lee');
};

alert(window.onload);//如果已经有window.onload,打印的是函数function

window.onload=function(){
 alert('Mr.lee');
} 

b:


alert(typeof window.onload);//一开始没有window.onolad,旧版火狐显示undefined,新版显示object,

window.onload=function(){
 alert('Lee');
};

alert(typeof window.onload);//如果已经有window.onload,所有浏览器都会显示function

window.onload=function(){
 alert('Mr.lee');
}

由此得出解决覆盖问题的方案。


 window.onload=function(){
 alert('Lee');
};

if(typeof window.onload=='function'){
 var saved=null;//保存上一个事件对象
 saved=window.onload;
}

//saved 就是window.onload,saved()相当于window.onload(),但是window.onload()不能执行的
//所以saved()相当于window.onload=function(){}

window.onload=function(){
 if(saved){
  saved();//执行上一个事件 window.onload=function(){}
 }
 alert('Mr.lee'); //执行本事件
}

问题二:事件切换器的实现 以id为box的div为例,实现背景颜色在red与blue之间的切换,并在切换前弹出提示框,代码如下:


window.onload=function(){
 var box=document.getElementById('box');
 box.className="red";
 box.onclick=function(){
  alert('Lee'); //只执行了一次
  blue.call(this);//通过匿名函数执行某一函数,那么里面的this就是代表的window,所以可以通过call传递
 };
}

function blue(){
 this.className="blue";
 this.onclick=red;
 
}

function red(){
 this.className="red";
 this.onclick=blue;
}

上述代码虽实现了切换功能,但弹窗仅执行了一次。


//添加事件函数
//obj相当于window
//type相当于onload
//fn相当于function(){}
function addEvent(obj,type,fn){
 //用于保存上一个事件
 var saved=null;
 if(typeof obj['on'+type]=='function'){
  saved=obj['on'+type];//保存上一个事件
 }
 obj['on'+type]=function(){
  if(saved){
   saved();
  }
  fn.call(this);
 }
 
}
addEvent(window,'load',function(){
 var box=document.getElementById("box");
 //addEvent(box,'click',function(){ //目的达到,每次都执行了,没有被覆盖
 // alert('ss');
 //});
 addEvent(box,'click',blue);
});

function red(){
 this.className="red";
 addEvent(box,'click',blue);
}

function blue(){
 this.className="blue";
 addEvent(box,'click',red);
}

//当不停的切换的时候,浏览器突然卡死,并且报错:too much recursion,太多的递归
//因为积累了太多的保存的事件
//解决方案,就是用完的事件,就立刻移除掉

针对上述代码中注释指出的错误,正确的解决方式如下:


//添加事件函数
//obj相当于window
//type相当于onload
//fn相当于function(){}
function addEvent(obj,type,fn){
 //用于保存上一个事件
 var saved=null;
 if(typeof obj['on'+type]=='function'){
  saved=obj['on'+type];//保存上一个事件
 }
 obj['on'+type]=function(){
  if(saved){
   saved();
  }
  fn.call(this);
 }
 
}


//当不停的切换的时候,浏览器突然卡死,并且报错:too much recursion,太多的递归
//因为积累了太多的保存的事件
//解决方案,就是用完的事件,就立刻移除掉


//移除事件函数
function removeEvent(obj,type){
 if(obj['on'+type]){
  obj['on'+type]=null;
 }
}


addEvent(window,'load',function(){
 var box=document.getElementById("box");
 //addEvent(box,'click',function(){ //目的达到,每次都执行了,没有被覆盖
 // alert('ss');
 //});
 addEvent(box,'click',blue);
});

function red(){
 this.className="red";
 removeEvent(this,'click');
 addEvent(box,'click',blue);
}

function blue(){
 this.className="blue";
 removeEvent(this,'click');
 addEvent(box,'click',red);
}

W3C标准事件处理函数详解

二、W3C标准事件处理函数 主要涉及addEventListener()与removeEventListener()两个方法。

//W3C标准提供的添加与移除事件方法 1. 解决覆盖问题,示例如下:


window.addEventListener('load',function(){
 alert('Lee');
},false);

window.addEventListener('load',function(){
 alert('Mr.Lee');
},false);

window.addEventListener('load',function(){
 alert('Mrs.Lee');
},false);

2. 解决相同函数被屏蔽的问题,示例如下:


window.addEventListener('load',init,false);
window.addEventListener('load',init,false);
window.addEventListener('load',init,false);
function init(){
 alert('Lee');
}

3. 解决this指向问题,示例如下: 例子1:


window.addEventListener('load',function(){
 var box=document.getElementById('box');
 box.addEventListener('click',function(){
  alert(this);
 },false);
},false);

例子2:


window.addEventListener('load',function(){
 var box=document.getElementById('box');
 box.addEventListener('click',blue,false);
},false);

function red(){
 this.className="red";
 this.removeEventListener('click',red,false);
 this.addEventListener('click',blue,false);
}

function blue(){
 this.className="blue";
 this.removeEventListener('click',blue,false);
 this.addEventListener('click',red,false);
}

4. 解决添加额外方法是否被覆盖或仅执行一次的问题,示例如下:


window.addEventListener('load',function(){
 var box=document.getElementById('box');
 box.addEventListener('click',function(){
  alert('Lee');
 },false);
 box.addEventListener('click',blue,false);
},false);

综上所述,W3C模型较为完美地解决了上述问题,使用体验良好。但需注意,IE8及更早版本浏览器不支持此标准,而是采用其私有事件模型;IE9及以上版本已完全支持W3C的这两个事件处理函数。

事件捕获与冒泡机制

W3C标准允许设置事件触发阶段为冒泡或捕获。

支持W3C标准的浏览器使用addEventListener(event, fn, useCapture)方法添加事件,其中第三个参数useCapture为Boolean值,用于指定事件是在捕获阶段还是冒泡阶段执行。对于不兼容W3C的浏览器(如IE),使用attachEvent()方法,该方法无相关设置参数,但IE事件模型默认在冒泡阶段执行(即useCapture为false时)。因此,将useCapture设置为false既能保证安全,也能实现跨浏览器兼容效果。

事件捕获阶段:事件从最顶层标签开始向下查找,直至到达事件目标(target)。 事件冒泡阶段:事件从事件目标(target)开始,向上冒泡直至页面最顶层标签。 事件的传播过程可以被阻止: 在W3C标准中,使用stopPropagation()方法; 在IE浏览器中,设置cancelBubble = true;

IE私有事件处理函数分析

三、IE私有事件处理函数

attachEvent()与detachEvent() IE实现了与DOM类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的参数:事件名称和函数。

在使用这两组函数时,需明确以下区别:1. IE不支持捕获阶段,仅支持冒泡;2. IE添加事件时无法屏蔽重复函数;3. IE中this指向window而非DOM对象;4. 在传统事件绑定中,IE无法接收event对象,但使用attachEvent()可以接收,且存在差异。

1. 覆盖问题已解决,但执行结果顺序为Mrs.Lee、Mr.Lee,最后是Lee。


window.attachEvent('onload',function(){
 alert('Lee');
});

window.attachEvent('onload',function(){
 alert('Mr.Lee');
});
window.attachEvent('onload',function(){
 alert('Mrs.Lee');
});

2. 相同函数屏蔽问题未解决。


window.attachEvent('onload',init);
window.attachEvent('onload',init);

function init(){
 alert('Lee');
}

3. 无法直接传递this,因为this指向window。需通过call方法处理。


window.attachEvent('onload',function(){
 var box=document.getElementById('box');
 box.attachEvent('onclick',function(){
  //alert(this===box);
  alert(this===window); //true
 });
});

此外,还可以通过window.event.srcElement获取目标对象,代码如下:


window.attachEvent('onload',function(){
 var box=document.getElementById('box');
 box.attachEvent('onclick',blue);
});

function red(){
 var that=window.event.srcElement;
 that.className="red";
 that.detachEvent('onclick',red);
 that.attachEvent('onclick',blue);
}

function blue(){
 var that=window.event.srcElement;
 that.className="blue";
 that.detachEvent('onclick',blue);
 that.attachEvent('onclick',red);
}

4. 添加额外方法是否被覆盖或仅执行一次的问题已解决。

在传统绑定中,IE无法像W3C标准那样通过传参接收event对象,但使用attachEvent()方法可以实现。


 window.attachEvent('onload',function(){
 var box=document.getElementById('box');
 box.onclick=function(evt){ //传统方法IE无法通过参数获取evt
  alert(evt);//undefined
 }
 box.attachEvent('onclick',function(evt){
  alert(evt);//object
  alert(evt.type);//click
  alert(evt.srcElement.tagName);//DIV
  alert(window.event.srcElement.tagName);//DIV
 });
});

跨浏览器兼容方案实现

跨浏览器兼容方案

跨浏览器添加事件的实现


function addEvent(obj,type,fn){
 if(obj.addEventListener){
  obj.addEventListener(type,fn,false);
 }else if(obj.attachEvent){
  obj.attachEvent('on'+type,fn);
 }
}

跨浏览器移除事件的实现


function removeEvent(obj,type,fn){
 if(obj.removeEventListener){
  obj.removeEventListener(type,fn,false);
 }else if(obj.detachEvent){
  obj.detachEvent('on'+type,fn);
 }
}

跨浏览器获取目标对象的方法


function getTarget(evt){
 if(evt.target){
  return evt.target;
 }else if(window.event.srcElement){
  return window.event.srcElement;
 }
}

调用方式示例:


addEvent(window,'load',function(){
 var box=document.getElementById('box');
 addEvent(box,'click',blue);
});


function red(evt){
 var that=getTarget(evt);
 that.className="red";
 removeEvent(that,'click',red);
 addEvent(that,'click',blue);
}

function blue(evt){
 var that=getTarget(evt);
 that.className="blue";
 removeEvent(that,'click',blue);
 addEvent(that,'click',red);
}

事件对象的其他补充说明

四、事件对象的其他补充说明

relatedTarget事件处理

W3C标准中提供了relatedTarget事件。 例如:


addEvent(window,'load',function(){
 var box=document.getElementById('box');
 addEvent(box,'mouseover',function(evt){
  alert(evt.relatedTarget); //得到移入box最近的那个DOM对象
 });
 
 addEvent(box,'mouseout',function(evt){
  alert(evt.relatedTarget); //从box移出最近的那个DOM对象
 });
});

IE提供了两组分别用于移入和移出的属性:fromElement和toElement,分别对应mouseover和mouseout事件。


addEvent(window,'load',function(){
 var box=document.getElementById('box');
 addEvent(box,'mouseover',function(){
  alert(window.event.fromElement.tagName); //得到移入box最近的那个DOM对象
 });
 
 addEvent(box,'mouseout',function(){
  alert(window.event.toElement.tagName); //从box移出最近的那个DOM对象
 });
});

PS:fromElement和toElement如果分别对应相反的鼠标事件,则没有任何实际意义。

剩余工作为进行跨浏览器兼容处理:


function getTarget(evt){
 var e=evt || window.event;
 if(e.srcElment){ //IE
  if(e.type=='mouseover'){
   return e.fromElement.tagName;
  }else if(e.type="mouseout"){
   return e.toElement.tagName;
  }
 }else if(e.relatedTarget){ //w3c
  return e.relatedTarget;
 }
}

屏蔽跳转操作

取消事件默认行为有一种不规范做法,即返回false。


link.onclick=function(){
 alert('Lee');
 return false;
}

PS:虽然return false可以实现该功能,但存在漏洞。 第一:必须写在代码最后,这导致前面的代码执行后,可能无法执行到return false; 第二:若将return false写在最前,则后续的自定义操作将失效。 因此,最佳方案是在代码最前面阻止默认行为,同时确保后续代码仍可执行。


link.onclick=function(evt){
 evt.preventDefault;//w3c,阻止默认行为
 alert('Lee');
}

link.onclick=function(evt){
 window.event.returnValue=false;//IE,阻止默认行为
 alert('Lee');
}

跨浏览器兼容实现如下:


function preDef(evt){
 var e=evt || window.event;
 if(e.preventDefault){
  e.preventDefault();
 }else{
  e.returnValue=false;
 }
}

右键菜单contextmenu事件 兼容处理如下:


function preDef(evt){
 var e=evt || window.event;
 if(e.preventDefault){
  e.preventDefault();
 }else{
  e.returnValue=false;
 }
}

addEvent(window,"load",function(){
 var body=document.getElementsByTagName('body')[0];
 addEvent(body,'contextmenu',function(evt){
  preDef(evt);
 })
});

PS:contextmenu事件使用频率较高,因此浏览器兼容性相对稳定。

卸载前事件:beforeunload 该事件可用于在用户离开页面时给出相应提示,如“离开”或“返回”操作。


addEvent(window,'beforeonload',function(){
 preDef(evt);
});

鼠标滚轮事件(mousewheel)和DOMMouseScroll 用于获取鼠标上下滚轮的距离。


addEvent(document,'mousewheel',function(evt){ //非火狐
 alert(getWD(evt));
});

addEvent(document,'DOMMouseScroll',function(evt){ //火狐
 alert(getWD(evt));
});

function getWD(evt){
 var e=evt|| window.event;
 if(e.wheelDelta){
  return e.wheelDelta;
 }else if(e.detail){ //火狐
  return -evt.detail*30;
 }
}

PS:通过浏览器检测可知,Firefox仅执行DOMMouseScroll事件。

DOMContentLoaded事件和readystatechange事件

DOMContentLoaded事件和readystatechange事件,均与DOM加载过程相关。

作者最新文章