本文较为浅显的说明了javascript中事件流的事件处理程序。
事件流描述的是从页面中接收事件的顺序,但ie的事件流是时间冒泡,而Netscape的事件流则是事件捕获
1.1.事件冒泡
即事件开始时由最具体的元素(即文档中嵌套层次最深的那个节点)接收,之后逐级向上传播
1.2.事件捕获
事件捕获的思想是不太具体的节点应该更早的接收到事件,而最具体的节点应该最后接收到事件
1.3DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。
IE8及更早版本不支持dom事件流
2事件处理程序
事件就是用户或浏览器自身执行的某种动作,诸如click,load等都是事件名字,而响应某个事件的函数,就叫做事件处理程序(或事件监听器),事件处理程序的名字以on开头,因此click的事件处理程序名字就是onclick,load则为onload.
2.1html事件处理程序
<input type="button" value="click me" onclick="alert(Clicked)">
单击按钮时,会弹出一个警示框,这个操作是通过指定onclick特性并将一些JavaScript代码作为他的值来定义的,由于这个值是JavaScript,因此不能再其中使用未经转义的html语法字符,若要使用则需如下修改
<input type="button" value="click me" onclick="alert($quot;Clicked$quot;)">
在html中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本,如下所示
<script type="text/javascript">
function showMessage(){
alert("Clicked");
}
</script>
<input type="button" value="click me" onclick="showMessage()"/>
在这个例子中,单击按钮就会调用showMessage事件,这个函数是独立在script中的,其次.这样指定事件处理程序,会有一些独到之处,首先,这样会创建一个封装着元素属性的函数.这个函数中有一个局部变量event,也就是事件对象
<input type="button" value="click me" onclick="alert(event.type)"/> //click(事件类型)
通过event变量,可以直接访问事件对象,你不用自己定义它,在这个函数内部,this值等于事件的目标元素
<input type="button" value="click me" onclick="alert(this.value)"/> //click me
关于动态创建函数,另一个有意思的地方是它的扩展作用域的方式,在这个函数内部,可以向访问局部变量一样访问document及该元素本身的成员,这个函数用with如下扩展作用域
function(){
with(document){
with(this){
//元素属性
}
}
}
如此一来,事件处理程序要访问自己的属性就容易多了.
如果当前元素是一个表单输入元素,则作用域中还会包含访问表单元素的入口,这个函数则变成了如下所示
function(){
with(document){
with(this.form){
with(this){
//元素属性
}
}
}
}
实际上,这样扩展作用域的方式,无非就是想让事件处理程序无需引用表单元素就能访问其他表单字段
<form>
<input type="text" name="username" value="">
<input type="button" value="echo username" onclick="alert(username.value)">
</form>
在这个例子中,单击按钮会显示文本框中的文本,值得注意的是,这里直接引用了username元素,不过,在html中指定事件处理程序有两个缺点
①存在一个时差问题,因为用户可能在html元素一出现在页面就触发相应事件,但当时的事件处理程序有可能尚不具备执行条件,假设showmessage函数在按钮下方,若用户在页面解析showmessage前就单击了按钮,就会引发错误,因此,很多html事件处理程序都会被封装在一个try-catch中,如下:
<input type="button" value="echo username" onclick="try{(showMessage);}catch(ex){}">
这样一来,浏览器有机会在处理错误之前,错误就被捕获了。
②这样扩展事件处理程序作用域链在不同浏览器中可能导致不同结果
③最后一个缺点则是html与javascript代码耦合度过高
3.IE事件处理程序
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent(),两个方法接收相同参数:事件处理程序名称与事件处理程序函数。,由于IE8及IE8-只支持冒泡。所以通过attachEvent()添加的都会被添加到冒泡阶段。
要使用attachEvent()为按钮添加一个事件处理程序,可以使用如下代码。
let btn = document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("Clicked");
})
在IE中使用attachEvent( )与使用DOM0级方法的主要区别在于事件处理程序的作用域,DOM0级为所属元素作用域内,而IE则是全局作用域中,因此this等于window
let btn = document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("this === window"); //true
})
在编写跨浏览器代码时,牢记这一区别非常重要
与addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序
let btn = document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("Clicked One");
})
btn.attachEvent("onclick",function(){
alert("Clicked Two");
})
注意!,这里与dom方法不同的是,事件触发是按照相反的顺序,即先弹出Click Two,后弹出Click One
使用了attachEvent之后可以使用detachEvent来移除,与addEventListener一样,两个方法的参数必须相同,也意味着匿名函数会移除失败
let btn = document.getElementById("myBtn");
let handle = function(){
alert("Clicked One");
};
btn.attachEvent("onclick",handle);
btn.attachEvent("onclick",handle);
支持IE事件处理程序的浏览器为IE和Opera
4.跨浏览器的事件处理程序
前面说过,只有冒泡会在不同浏览器中有差异,所以我们只需要关注冒泡阶段.
第一个要创建的方法是addHandler(),它的职责是视情况分别使用DOM0级方法和DOM2级方法或IE方法来添加事件,addHandler()接收三个参数:要操作的元素,事件名称和事件处理程序的名称,与之对应的方法为removeHandler(),方法如下
let 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.addEventListener){
element.removeEventListener(type,handler,false);
}else if(element.attachEvent){
element.detachEvent("on"+type,handler);
}else{
element["on"+type] = null;
}
}
}
两个方法都会先检测传入的元素中是否存在dom2级方法,然后检测ie方法,最后检测dom0级方法.
可以如下使用eventUtil对象:
let btn = docuemnt.getElementById("myBtn");
let handler = function () {
alert("clicked");
}
eventUtil.addHandler('btn','click',handler);
eventUtil.removeHandler('btn','click',handler);
注意!addHander和removeHander并没有考虑到所有浏览器的问题,例如IE的兼容性,但目前只支持ie的浏览器并不多,并不会太影响使用
最后简单提一下关于dom事件分级
1.DOM0级事件
在了解DOM0级事件之前,我们有必要先了解下HTML事件处理程序,也是最早的这一种的事件处理方式,代码如下:
<button type="button" onclick="showFn()"></button>
<script>
function showFn() {
alert('Hello World');
}
</script>
以上代码我们通过直接在HTML代码里定义了一个onclick的属性触发showFn方法,这样的事件处理程序最大的缺点就是HTML于JS强耦合,我们一旦需要修改函数名就得修改两个地方。当然其优点是不需要操作DOM来完成事件的绑定。
那么什么是DOM0级处理事件呢?DOM0级事件就是将一个函数赋值给一个事件处理属性,比如:
<button id="btn" type="button"></button>
<script>
var btn = document.getElementById('btn');
btn.onclick = function() {
alert('Hello World');
}
// btn.onclick = null; 解绑事件
</script>
以上代码我们给button定义了一个id,通过JS获取到了这个id的按钮,并将一个函数赋值给了一个事件处理属性onclick,这样的方法便是DOM0级处理事件的体现。我们可以通过给事件处理属性赋值null来解绑事件。
DOM0级事件处理程序的缺点在于一个处理程序无法同时绑定多个处理函数,比如我还想在按钮点击事件上加上另外一个函数。
2.DOM2级事件
DOM2级事件在DOM0级事件的基础上弥补了一个处理程序无法同时绑定多个处理函数的缺点,允许给一个处理程序添加多个处理函数。代码如下:
<button id="btn" type="button"></button>
<script>
var btn = document.getElementById('btn');
function showFn() {
alert('Hello World');
}
btn.addEventListener('click', showFn, false);
// btn.removeEventListener('click', showFn, false); 解绑事件
</script>
DOM2级事件定义了addEventListener和removeEventListener两个方法,分别用来绑定和解绑事件,方法中包含3个参数,分别是绑定的事件处理属性名称(不包含on)、处理函数和是否在捕获时执行事件处理函数。如果我们还需要添加一个鼠标移入的方法,只需要:
btn.addEventListener('mouseover', showFn, false);
这样点击按钮和鼠标移入时都将触发showFn方法。
需要注意的是IE8级以下版本不支持addEventListener和removeEventListener,需要用attachEvent和detachEvent来实现:
btn.attachEvent('onclick', showFn); // 绑定事件
btn.detachEvent('onclick', showFn); // 解绑事件
这里我们不需要传入第三个参数,因为IE8级以下版本只支持冒泡型事件。