一、JS event 的浏览器兼容
说到JS事件,不能不先讲一下事件流
1 事件流的定义:事件流是指从页面中接收事件的顺序
如下图所示,假设有四个圆层叠在一起,如果我们单击图中最里面的那个圆,那么我们我们单击到的目标是谁呢?黑色的圆,淡紫色的圆,卡其色的圆,还是最外面的粉色的圆,这就提出了冒泡流和捕获流
2 冒泡流与捕获流
假设还是上面那个例子,我们用ABCD来标示各个圆,如果单击了最上面的圆D,那么会出现下面的情况,先是最不具体的元素(本例中,最不具体的元素是指A)接收到事件,然后一级一级向最具 体的元素(本例中,最具体的元素是D)传播,这个传播阶段就称为事件的捕获阶段,最具体的元素接收到事件的阶段属于目标阶段,当目标元素接收到事件后,开始逐级向最不具体的元素传播, 这一阶段属于冒泡阶段。
通过上面的讲解,我们给出捕获流和冒泡流的概念
捕获流:不太具体的节点更早接收到事件,而最具体的元素最后接收到事件,在事件到达预订目标之前捕获它
冒泡流:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的元素
3 事件侦听器
事件就是用户或者浏览器自身执行的某种动作,例如click、load等,而响应某个事件 函数就是事件侦听器或者是事件处理程序
为事件指定处理程序的方式有下面几种
3.1 html事件处理程序:利用与相应的事件处理程序同名的html特性来指定事件处理程序
3.2 DOM0级事件处理程序:将一个函数赋值给一个事件处理程序属性
3.3 DOM2级事件处理程序:对其绑定事件处理程序
<input type="button" value="click me" onclick="showMessage()" />
<!--第一种为事件指定处理程序的方式----html事件处理程序--> function showMessage(){ alert("click"); } //第二种为事件指定处理程序的方式---DOM0级 var btn=document.getElementById("btn"); btn.onclick=function(){ alert("click"); } //第三种为事件指定处理程序的方式---DOM2级 var btn=document.getElementById("btn"); btn.addEventListener("click",function(event){...},false);
btn.addEventListener("click",function(event){...},false); //第三种为事件指定处理程序的方式可以添加多个事件处理程序
第一种方式的缺点:一、时差问题,例如showMessage()函数定义在页面的最底部,而用户在页面解析showMessage方法之前单击了上面的按钮,就会引发错误,二、紧耦合html与js代码紧密耦合,如果要更换事件处理程序,即要修改js代码又要修改html的绑定处的代码
第二种方式的优点:简单,跨浏览器,使用这种方式添加的事件处理程序会在事件流的冒泡阶段被处理,缺点:不能添加多个事件处理程序
注意,虽然addEventListener方式可以控制在冒泡阶段还是捕获节点触发事件处理程序,但是大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这是由于这样做可以最大限度地兼容各种浏览器,因此不建议在事件捕获阶段注册事件处理程序
4 跨浏览器的事件处理程序
开发能够隔离浏览器差异的JS库,保证处理事件的代码能在大多数浏览器下一致地运行,只要做到适当地使用能力检测,并关注冒泡阶段即可
4.1 下面我们罗列一下DOM的事件对象与IE的事件对象之间的差异
具有差异的相关方面 |
DOM |
IE |
添加事件处理程序的方式 以及参数的不同 |
addEventListener(type,handler,true/false) 参数:type是指事件的类型,直接使用 handler是指要添加的事件处理程序 true是指在捕获阶段调用handler,false 是指在冒泡阶段调用handler |
attachEvent(type,handler) 参数:type同样是指事件的类型,但是需要"on"+type 另外IE添加事件处理程序的方法没有第三个参数,是因为IE只在事件的冒泡阶段调用事件处理程序 |
事件对象 (在触发DOM上的某个事件时,会产生一个事件对象event,该对象包含了所有与该事件相关的信息,例如导致事件发生的元素,事件的类型等) |
兼容DOM的浏览器将一个event对象传入到事件处理程序中,该对象只存在与事件处理程序执行期间,一旦事件处理程序执行完毕,event对象就会被销毁 |
IE的event对象是作为window对象的一个属性存在的 |
取消事件的默认行为 |
event.preventDefault() |
event.returnValue=false |
阻止冒泡 |
event.stopPropagation() |
event.cancelBubble=true |
获取事件的目标元素 |
event.target |
event.srcElement |
获取事件的相关元素 |
event.relatedTarget |
在mouseover事件出发时event.fromElement保存的是相关元素,而在mouseout事件触发时,event.toElement保存的是相关元素 |
同一元素的同一事件上绑定的多个事件处理程序触发的顺序 | 按照绑定的先后顺序依次触发 | 触发顺序与绑定的顺序相反 |
4.2 下面是一个几乎包含了事件方面兼容性的js代码
var Event={ //绑定事件处理程序的不同 addHandler:function(element,type,handler){ if(element.addEventListener){ //DOM element.addEventListener(type,handler,false); }else if(element.attachEvent){ //IE element.attachEvent("on"+type,handler); }else{ element["on"+type]=handler; } }, //获取事件对象event getEvent:function(event){ return event?event:window.event; }, //获取目标元素 getTarget:function(event){ return event.target||event.srcElement; }, //获取相关元素 getRelatedTarget:function(event){ if(event.relatedTarget){ return event.relatedTarget; }else if(event.toElement){ return event.toElement; }else if(event.fromElement){ return event.fromElement; }else{ return null; } }, //阻止默认事件 preventDefault:function(e){ if(e.preventDefault){ e.preventDefault(); }else{ e.returnValue=false; } }, //阻止冒泡 stopPropagation:function(e){ if(e.stopPropagation){ e.stopPropagation(); }else{ e.cancelBubble=true; } }, //移除事件处理程序 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; } } };
5 同一事件的同一元素上绑定的多个事件处理程序触发的顺序
首先针对DOM,如果元素为最具体的元素,不管事件是绑定在冒泡阶段还是捕获阶段,事件触发的顺序仅仅与事件绑定的顺序相关
var d=document.getElementById("my"); d.addEventListener("click",function(){ alert(1); },false);//冒泡阶段 d.addEventListener("click",function(){ alert(2); },true);//捕获阶段 d.addEventListener("click",function(){ alert(3); },false);//冒泡阶段 //触发顺序为1 2 3,可见只要事件绑定在最具体的元素上,
//事件触发的顺序与绑定在冒泡和捕获阶段无关,只与绑定的顺序相关
//因为是最具体的元素,所以事件触发是在目标阶段,与在绑定事件处理程序时说明冒泡阶段或者捕获阶段都无关
针对DOM,如果元素不是最具体的元素,那么事件触发的顺序与事件绑定在冒泡阶段还是捕获阶段就息息相关了
var outer_div=document.getElementById("outer_div");//不具体的元素 var inner_div=document.getElementById("inner_div");//具体的元素 outer_div.addEventListener("click",function(){ alert("我是第一个追加在外层div上的事件,我触发的阶段是冒泡阶段"); },false);//冒泡阶段 outer_div.addEventListener("click",function(){ alert("我是第二个追加在外层div上的事件,我触发的阶段是捕获阶段"); },true);//捕获阶段 outer_div.addEventListener("click",function(){ alert("我是第三个追加在外层div上的事件,我触发的阶段是冒泡阶段"); },false);//冒泡阶段 inner_div.addEventListener("click",function(){ alert("我是第一个追加在内层div上的事件,我触发的阶段是冒泡阶段"); },false);//冒泡阶段 inner_div.addEventListener("click",function(){ alert("我是第二个追加在内层div上的事件,我触发的阶段是捕获阶段"); },true);//捕获阶段 inner_div.addEventListener("click",function(){ alert("我是第三个追加在内层div上的事件,我触发的阶段是冒泡阶段"); },false);//冒泡阶段
//我是第二个追加在外层div上的事件,我触发的阶段是捕获阶段
//我是第一个追加在内层div上的事件,我触发的阶段是冒泡阶段
//我是第二个追加在内层div上的事件,我触发的阶段是捕获阶段
//我是第三个追加在内层div上的事件,我触发的阶段是冒泡阶段
//我是第一个追加在外层div上的事件,我触发的阶段是冒泡阶段
//我是第三个追加在外层div上的事件,我触发的阶段是冒泡阶段
由上图可知,首先触发的是绑定在outer_div捕获阶段的事件,其次是绑定在inner_div上面的事件,由于inner_div是最具体的元素,位于目标阶段,因此绑定在它上面的事件触发的顺序与绑定事件的顺序相关,最后是绑定在outer_div冒泡阶段的事件
6 事件委托当添加到页面的事件处理程序过多时,就会引起性能的降低,原因如下:一、每个函数都是对象,都会占用内存,内存中的对象越多,性能就越差;二、指定事件处理程序导致对DOM的访问次数增多,会延迟整个页面的交互就绪事件,因此我们给出了事件委托的概念
6.1 事件委托的定义:利用事件的冒泡,只指定一个事件处理程序,就可以管理页面中某一类型的所有事件,即向DOM树尽可能高的层次添加事件处理程序
6.2 示例
//结合上面的跨浏览器的事件处理程序,将事件绑定在DOM树的最高层document上 Event.addHandler(document,"click",function(event){ event=Event.getEvent(event); var target=Event.getTarget(event); switch(target.id){ case "ddTest": alert("my name is 'dTest',this is my first event"); alert("my name is 'dTest',this is my second event"); break; case "aaTest": alert("my name is 'aTest',this is my first event"); break; } });
7 模拟事件
7.1 事件可以通过客户操作或者浏览器的功能触发,也可以使用JS在任意时刻来触发特定的事件,在测试web应用程序时,模拟事件就显得尤为重要,DOM2规范规定了模拟特定事件的方式,Opera、火狐、Chrome和Safari都支持该规范,IE有自己模拟事件的方式,下面我们先讲解DOM的模拟事件
7.2 DOM的事件模拟(共分为三个步骤):1 创建事件对象,2 初始化步骤一中创建的事件对象,3 触发事件
7.2.1 创建事件对象-----document.createEvent()方法,该方法接受一个参数,即表示要创建的事件类型的字符串,注意DOM2级事件没有规定键盘事件,DOM3中规定了DOM事件,但是当前并没有浏览器支持,但是通过现有方法可以模拟键盘事件
参数: UIEvents:一般化的UI事件,鼠标事件和键盘事件都继承自UI事件
参数: MouseEvents:一般化的鼠标事件
参数:MutationEvents:一般化的DOM变动事件
参数:HTMLEvents:一般化的HTML事件
7.2.2 使用与事件有关的信息对创建的事件对象进行初始化,而这个初始化方法与所创建的事件类型相关,下面我们详细讲解初始化鼠标事件的方法和模拟键盘事件的方法等
7.2.2.1 模拟鼠标事件
1,document.createEvent("MouseEvent");
2,初始化鼠标对象的方法---initMouseEvent方法
3,触发事件--dispatchEvent(event)方法
var btn=document.getElementById("myBtn"); btn.addEventListener("click",function(){ alert(123); },false); var event=document.createEvent("MouseEvents"); event.initMouseEvent("click",true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null); /*initMouseEvent的参数 type:触发事件的类型,例如"click" bubble:事件是否应该冒泡 cancelable:表示事件是否可以取消 view:与事件相关的视图,一般设置为document.defaultView
//前面的这四个参数是至关重要的 detail:数字,一般只有事件处理程序使用,通常设置为0 sreenX/sreenY:事件相对于屏幕的X/Y坐标 clientX/clientY:事件相对于视口的X/Y坐标 ctrlKey/altKey/shiftKey/metaKey:表示是否按下了ctrl/alt/shift/meta键,true/false,一般为false button:表示按下了哪一个鼠标键,一般为0 relatedTarget:表示与事件相关的元素,该参数只在mouseover或者mouseout时使用 */ btn.dispatchEvent(event); //触发btn的模拟click事件,因此即使不单击btn按钮,也会弹出123的
注意:safari3不完全支持鼠标事件,因此在创建事件对象时,会返回一个不包含initMouseEvent方法的event对象,
解决方案:在创建事件对象时,使用一般化的UI事件(鼠标事件和键盘事件都继承了UI事件),在创建UI事件时会返回一个包含initEvent方法的event对象,initEvent方法包含了三个参数,分别是事件类 型,是否冒泡以及事件是否可以取消,我 们可以继续向其创建的event对象添加鼠标事件的其他属性
//针对safari浏览器 var event=document.createEvent("UIEvents");
/*initMouseEvent的参数 type:触发事件的类型,例如"click" bubble:事件是否应该冒泡 cancelable:表示事件是否可以取消
*/
event.initEvent("click",true,true);
event.view=document.defaultView;
event.detail=0;
event.screenX=0;
event.screenY=0;
event.clientX=0;
event.clientY=0;
event.ctrlKey=false;
event.altKey=false;
event.shiftKey=false;
event.metaKey=false;
event.button=0;
event.relatedTarget=null;
btn.dispatchEvent(event);
7.2.2.2 模拟键盘事件
DOM并没有对键盘事件作出规定,因此并不是所有的浏览器都可以精确模拟键盘事件
针对火狐浏览器(了解一下):
1 创建事件---createEevnt,参数KeyEvents,
2 初始化initKeyEvent方法
3 触发 dispatchEvent(event)
var myTextBox=document.getElementById("myTextBox"); var event=document.createEvent("KeyEvents"); /*initKeyEvent的参数 type:'keypress',//事件类型 bubbles://表明事件是否应该冒泡 cancelable:true/false//表明事件是否可以取消 view://document.defaultView, ctrlKey:true/false, altKey:true/false, shiftKey:true/false, metaKey:true/false, keyCode:整数//被按下或者释放的键的键码 charCode:整数//通过按键生成的字符的ASC码,该参数对keypress事件有用,模拟为0 */ event.initKeyEvent("keypress",true,true,document.defaultView,false,false,false,false,65,65); myTextBox.dispatchEvent(event);//虽然会触发键盘事件,但是确不会向文本框中写入文本,这是由于无法精确模拟键盘事件所造成的
7.3 IE的事件模拟:1 创建事件对象-----createEventObject 2 给步骤一的事件对象追加必要的信息 3 触发事件--fireEvent
var btn=document.getElementById("myBtn"); var event=document.createEventObject();//此处没有参数 event.screenX=100; event.screenY=100; event.clientX=100; event.screenY=100; event.ctrlKey=false; event.shiftKey=false; event.button=0; btn.fireEvent("onclick",event);//注意此处的参数一是type,即事件类型,参数二是事件对象
8 事件类型:
1 、UI事件:用户界面事件,即用户与页面上的元素交互时触发的(主要与焦点有关,但是支持UI事件的浏览器很少,因此不推荐使用,这里就不再介绍了)
2、 鼠标事件:用户在页面上操作鼠标触发的
3 、键盘事件
4 、HTML事件:当浏览器窗口发生变化或者发生特定的客户/服务器交互时触发
5 、 变动(mutation)事件:当底层的DOM结构发生变化时触发
8.1 鼠标事件:包括mousedown mouseup click dbclick
1 鼠标事件可以获取到的坐标信息:clientX/clientY和screenX/screenY表示鼠标事件相对于视口和屏幕的坐标信息
2 修改键:按下鼠标的同时,按下键盘上的某些键可以影响操作,这里的修改键有shift/ctrl/alt/meta键,它们经常用来修改鼠标事件的行为,为此DOM定义了四个属性值与这四个键相对应,分别为 event.shiftEvent/event.ctrlKey/event.altKey/event.metaKey,注意IE不支持meta键
3 相关元素:针对mouseover事件来说,事件的目标是获得光标的元素,相关元素是失去光标的元素;而针对mouseout事件而言,相关元素则是获取焦点的元素,目标元素是失去焦点的那个元素
4 鼠标按钮:mousedown和mouseup事件的event对象有一个button属性,该属性表示按下/释放按钮,DOM的button属性有值0:主鼠标按钮(鼠标左键);1:鼠标滚轮按钮;2:次鼠标按钮(鼠标右键)
而IE的button属性与DOM的button属性差别很大
0 | 没有按下按钮 |
1 | 按下左键 |
2 | 按下右键 |
3 | 同时按下左键和右键 |
4 | 按下中间的鼠标按钮 |
5 | 主中 |
6 | 次中 |
7 | 主次中 |