【读书笔记】JS高级程序设计·事件

1 事件流

在我们点击某个按钮的时候,其实也点击了包含它的容器,以及整个页面。假使我们给按钮的点击添加了某个事件的触发,但我们也给对包含它的容器的点击添加了事件的触发,那一般情况下我们肯定不能舍去其中某个事件的发生,这就有了多个事件的发生,既然有了多个事件,就必定要确定一个执行的顺序。

所谓的事件流就是从页面接收事件的顺序,但是有两种不同的方法,一种是事件冒泡流(IE团队制定),一种是事件捕获流(Netscape团队制定)。
事件冒泡顾名思义,即从底端一直上升到顶端,所以是从最小的单位触发的事件开始的。
例子:

<!doctype>
<html>
<head>
	<title>Event Bubbling Example</title>
</head>
	<div id=”myDiv”>Click me!</div>
</html>

如果你点击了ID为myDiv这个div,那么click事件会从div传到body,再传到html,最后到达document。理论是如此,现实的浏览器在具体实现上会有一些差别。比如IE5.5以前的版本会跳过html直接到document。而IE9、Firefox、Chrome和Safari则最多冒泡到window对象。
事件捕获则与事件冒泡的顺序刚好相反,从最大的元素开始一级一级捕获到最终的目标。上段的例子中如果是事件捕获,则顺序是从document一直到div。新版本的浏览器能支持事件流模型,但是因为老版本浏览器不支持,所以这种用的人比较少。
在DOM2规范中,定义事件流应该包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。还是以之前那个例子为例,DOM2规范中先从document开始到div(捕获阶段),到达div之后就是处于目标阶段,再从div又升到了document(冒泡阶段)。在捕获阶段不会接受事件,也就是开始document到body这段时间不接收发生事件,而下一阶段就是处于目标阶段,发生事件后,冒泡阶段开始发生,事件又传回文档。但实际情况,支持DOM事件流的浏览器(IE及更早版本不支持)会在捕获阶段触发事件对象的事件。

2 事件处理程序

事件是用户或浏览器自身执行的某种动作,而要响应某个事件的函数就叫做事件处理程序(或事件侦听器)。

2.1 HTML中的事件处理程序

<input type=”button” value=”click me” onclick=”alert(‘yes’)” />

onclick包含的值是js代码,所以不能使用未经转义的HTML语法字符,像&、<、>、””之类的,如果想使用双引号,我们可以用"代替。
这种处理程序会创建一个封装着元素属性值的函数,有一个局部变量event,即事件对象。它还会有一个扩展作用域的方式,也就是在这个局部中你可以访问到document以及元素本身的成员,它使用了with的方法来扩展作用域。

<input type=”button” value=”click me” onclick=”alert(value)” />

这句代码和之前的呈现的效果一样。在表单中,作用于还包括了表单的入口,即你可以直接调用表单中其他的元素。

<form method=”post”>
	<input type=”text” name=”username” value=”” >
	<input type=”button” value=”Echo username” onclick=”alert(username.value)” >
</form>

不过HTML事件处理程序存在几个缺点,其一,存在时差,可能在js代码未加载完全的时候点击了HTML元素触发事件,从而产生错误,所以一般会捕捉错误来处理。

<input type=”button” value=”click me” onclick=”try{showMessage();}catch(ex){}”>

其二是扩展事件处理程序的作用域链在不同的浏览器会导致不同的结果。最后一个就是耦合度太高,不方便修改。

2.2 DOM0级事件处理程序

这种处理程序是通过把一个函数赋值给一个事件处理程序属性来控制。至今仍然被所有线代浏览器支持,因为它简单并且具有跨浏览器的优势,但它只能添加一个事件处理程序。

var btn = document.getElementById(“myBtn”);
btn.onclick = function () {
	alert(“yes”);
}

每个元素都有自己的事件处理程序属性,他们通常是全小写,像上面一样为它的属性设置一个函数就可以指定处理程序。但在代码运行之前并不会为他们指定,如果代码位于按钮后面,就可能在加载好之前不起作用。在这里,onclick被认为是元素的方法,所以事件处理程序是在元素的作用域中运行的,也就是在这里调用this是这个btn元素。
用这种方法添加的时间处理程序会在事件流的冒泡阶段被处理。可以指定它为null来删除事件处理程序。

2.3 DOM2级事件处理程序

它定义了两个方法:处理指定事件处理程序addEventListener()和删除事件处理程序removeEventListener()。他们都接受三个参数:事件名,处理函数,布尔值。如果这个布尔值是true,表示在捕获阶段调用事件捕捉,反之则在冒泡阶段调用事件处理程序。使用DOM2级事件处理程序的好处是可以添加多个事件处理程序。

var btn = document.getElementById(“myBtn”);
btn.addEventListener(“click”,function(){
	alert(this.id);
},false);
btn.addEventListener(“click”,function(){
	alert(“hi”);
},false);

在这个例子里面,这两个事件会按照添加它们的顺序触发,所以先显示ID再显示”hi”。
通过addEventListener添加的事件只能通过removeEventListener来删除,但是传入的参数必须要与添加程序的参数一样,所以匿名函数无法移除,因为第二次即使传入了一样的函数写法但这已经算是一个新的函数了。

var btn = document.getElementById(“myBtn”);
var handler = function() {
	alert(this.id);
};
btn.addEventListener(“click”,handler,false);
btn.removeEventListener(“click”,handler,false);

一般都将事件流添加到冒泡阶段,可以最大兼容浏览器。所以最好只在想在事件达到目标之前截获它的时候把事件处理程序添加到捕获阶段。

2.4 IE事件处理程序

IE中有两个与DOM类似的方法:attachEvent()和detachEvent(),都接收两个参数:事件处理程序名称和事件处理函数。这样添加的事件处理程序都被添加到冒泡阶段。
这种事件处理程序方法与DOM0级方法主要区别在于处理程序的作用域。DOM0级的方法会在其所属的作用域内运行,而attachEvent()方法在全局作用域中运行,也就是说this等于window。

vat btn = document.getElementById(“myBtn”);
btn.attachEvent(“onclick”,function(){
	alert(this === window)  //true
});

使用attachEvent()也可以为元素添加多个事件处理程序,不过他是以相反的顺序来执行。在移除事件处理程序上与DOM处理程序一样,不能移除匿名函数。

2.5 跨浏览器的事件处理程序

根据前面的认识,我们掌握了他们的一些区别。为了能够打造一个兼容的事件处理程序,我们创建了一个EventUtil对象,对于多个处理程序的执行顺序和作用域的差异未能考虑到,但它大体能保证添加与移除事件处理程序。

var EventUtil = {
	addHandler: function(element,type,handler) {
	if(element.addEventListener) {
		//传入参数事件类型,执行函数,选用冒泡事件流
	element.addEventListener(type,handler,false);
}	else if(element.attachEvent)	{
		//为了在IE8及更早版本支持,必须加一个on
	element.attachEvent(“on”+type,handler);
}	else {
	//如果前两种都不支持,则选用兼容性最好的DOM0事件处理程序
	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;
}
}
};

3 事件对象

1 HTML事件处理程也保存着event对象。

<input type=”button” value=”click me” onclick=”alert(event.type)” />

2 DOM0中的事件对象
事件处理程序中会被传入一个event对象。

var btn = document.getElementById(“muBtn”);
btn.onclick = function(event){
	alert(event.type);
}

*event的所有属性都是只读。
·关于currentTarget和target
在事件处理程序内部,this始终等于currentTarget的值,target只包含事件的实际目标。
直接将事件处理程序指定给目标元素,则this、currentTarget和target都包含相同值。

document.getElementById(“mybtn”)).onclick = function(event){
alert(event.currentTarget == this) //true,this始终等于currentTarget,目标是mybtn;
alert(event.target === this) //true;
}
如果事件处理程序在父节点中:

document.body.onclick = function(event){
alert(event.currentTarget === document.body) //true;
alert(this === document.body ) //true,this始终等于currentTarget;
alert(event.target === document.getElementById(“mybtn”)); //true,mybtn是实际目标;
}
click事件冒泡到了document.body上。
·关于type
通过一个函数处理多个事件,可以选择用type属性。

var handler = function(event){
switch(event.type){
case “click”: //…
break;
case “mouseover”: //….
break;
}
};
document.getElementById(“mybtn”).onclick = handler;
document.getElementById(“mybtn”).onmouseover = handler;

3 IE中的事件对象
IE中的event对象有几种不同的访问方式。
·使用HTML事件处理程序,有一个名为event的变量可直接使用:

<input type=”button” value=”click me” onclick=”alert(event.type)” />

·使用DOM0级方法添加事件处理程序时,event作为window对象一个属性存在:

var btn = document.getElementById(“mybtn”);
btn.onclick = function(){
var event = window.event;
alert(event.type);
}

·使用IE添加方法(attachEvent)时,不仅window有属性,event也作为对象参数传入:

var btn = document.getElementById(“mybtn”);
btn.attachEvent(“onclick”,function(event){
alert(event.type);
});

IE的全部event对象所拥有的属性和方法:
cancleBubble:可读写。默认false,是true表示取消事件冒泡
returnValue:默认ture,值为false表示取消默认行为。
srcElement:事件的目标
type:事件类型

事件处理程序的作用域是根据指定方法确定的,所以this不一定都是作用目标,
所以用event.srcElement最保险。

btn.onclick = function(){
	alert(window.event.srcElement === this);  //true
});
btn.attachEvent(“onclick”,function(){
	alert(event.srcElement === this); //false
});

4 跨浏览器的event对象写法

var EventUtil = {
	getEvent: function(event) {
	return event:window.event //当IE中用DOM0添加事件,event是undefined
},
getTarget:function(event){
	return event.target||event.srcElement;
},
preventDefault:function(event){
	if(event.preventDefault){
	event.preventDefault();
}else{
	event.returnValue = false;
}
},
stopPropagation:function(event){
	if(event.stopPropagation){
	event.stopPropagation();
}else{
	event.cancelBubble = true;
}
}
}
posted @ 2022-11-13 18:10  章鱼小年糕  阅读(12)  评论(0编辑  收藏  举报