JavaScript高级程序设计:第十三章

时间:2021-07-16 07:46:39

第十三章

一、理解事件流

事件流描述的是从页面中接收事件的顺序。

1.事件冒泡

IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。以下面的HTML页面为例:

<!DOCTYPE html>

<html>

<head>

<title>Event Bubling Example</title>

</head>

<body>

<div id="myDiv">Click Me</div>

</body>

</html>

如果你单机了页面中的<div>元素,那么这个click事件会按照如下顺序传播:

①<div>

②<body>

③<html>

④document

也就是说,click事件首先在<div>元素上发生,而这个元素就是我们单击的元素。然后click事件沿DOM树向上传播,在每一级节点上都会发生,直至传播到document对象。

2.事件捕获

另一种事件流叫做事件捕获。事件捕获的思想是不太具体的节点应该更早的接收到事件。如果仍以前面的页面作为演示事件捕获的例子,那么单击<div>元素就会以下列顺序触发click事件。

①document

②<html>

③<body>

④<div>

在事件捕获过程中,document对象首先接收到click事件,然后事件沿DOM树依次向下,一直传播到事件的实际目标,即<div>元素。

3.DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件的捕获阶段、处于目标阶段和事件冒泡阶段。

二、事件处理程序

1.HTML事件处理程序

某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。

在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本,如下面的例子所示:

<script  type = “text/javascript”>

function  showMessage(){

alert(“Hello world!”);

}

</script>

<input  type = “button”  value = “Click  Me”  onclick = “showMessage()”  />

在这个例子中,单击按钮就会调用showMessage()函数。这个函数是在一个独立的<script>元素中定义的,当然代码也可以被包含在一个外部文件中,事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。

这样指定事件处理程序有一些独到之处。首先会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象:

<!—输出“click”-->

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

通过event变量,可以直接访问事件对象,你不用自己定义它,也不用从函数的参数列表中读取。在这个函数内部,this值等于事件的目标元素,例如:

<!—输出”Click Me”-->

<input type=”button” value=”Click Me” onclick=”alert(this.value)”>

关于这个动态创建函数,另一个有意思的地方是它扩展作用域的方式。在这个函数内部,可以像访问局部变量一样访问document及该元素本身的成员。

2.DOM0级事件处理程序

通过javascript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。

每个元素,都有自己的事件处理程序属性,这些属性通常全部小写,例如onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序,如下所示:

var btn = document.getElementById(“myBtn”) ;

btn.onclick = function() {

alert(“Clicked”) ;

} ;

在此,我们通过文档对象取得了一个按钮的引用,然后为它指定了onclick事件处理程序。使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this引用当前元素。来看一个例子:

var btn = document.getElementById(“myBtn”) ;

btn.onclick = function() {

alert(this.id) ;         //”myBtn”

} ;

单击按钮显示的是元素的ID,这个ID是通过this.id取得的。

也可以通过删除DOM0级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设置为null即可:

btn.onclick = null ; //删除事件处理程序

3.DOM2级事件处理程序

“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener() 和 removeEventListener()。所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序和函数的一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

使用DMO2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。来看下面的例子。

var btn = document.getElementById("myBtn");

btn.addEventListener("click",function(){

alert(this.id);

},false);

btn.addEventListener("click",function(){

alert("Hello world!");

},false);

通过addEventListener() 添加的事件处理程序只能用removeEventListener()来移除;移除同时传入的参数与添加处理程序时使用的参数相同。这意味着通过addEventListener() 添加的匿名函数将无法移除。

4.IE事件处理程序

IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接收相同的2个参数:事件处理程序名称好事件处理程序函数。与DOM方法不同的是:

(1)DOM0级方法事件处理程序会在其所属元素的作用域中运行,而attachEvent()方法在全局作用域中运行。

(2)attachEvent()事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。

5.跨浏览器的事件处理程序

第一个要创建的方法是addHandler(),它的职责是视情况分别使用DOM0级方法、DOM2级方法或IE方法来添加事件。这个方法属于一个名叫EventUtil的对象,它接受3个参数:要操作的元素、事件名称和事件处理程序函数。

与addHandler()对应的方法是removeHandler(),它也接受相同的参数。这个方法的职责是移除之前添加的事件处理程序——无论该事件处理程序是采取什么方式添加到元素中的,如果其他方法无效,默认采用DOM0级方法。

三、事件对象

1.DOM中的事件对象

兼容DOM的浏览器会将一个event对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法,都会传入event对象。来看下面的例子:

var btn = document.getElementById(“myBtn”) ;

btn.onclick = function(event){

           alert(”event.type”);

};

btn.addEventListener(“click” ,function(event){

alert(event.type);      //“click”

},false);

这个例子中的两个事件处理程序都会弹出一个警告框,显示由event.type属性表示的事件类型。

2.IE中的时间对象

与访问DOM中的event对象不同,要访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。来看下面的例子:

var btn = document.getElementById(“myBtn”) ;

btn.onclick = function() {

var event = window.event ;

alert(event.type) ;       //”click”

};

通过window.event取得了event对象,并检测了被触发事件的类型。

3.跨浏览器的事件对象

虽然DOM和IE中的event对象不同,但基于它们之间的相似性依旧可以拿出跨浏览器的方案来。IE中event对象的全部信息和方法DOM对象中都有,只不过实现方式不一样。不过,这种对应关系让实现两种事件模型之间的映射非常容易。

4.事件类型

(1)UI事件,当用户与页面上的元素交互时触发;

(2)焦点事件,当元素获得或是去焦点时触发;

(3)鼠标事件,当用户通过鼠标在页面上执行操作时触发;

(4)滚轮事件,当使用鼠标滚轮时触发;

(5)文本事件,当在文档中输入文本时触发;

(6)键盘事件,当用户通过键盘在页面上执行操作时触发;

(7)合成事件,当为IEM输入字符时触发;

(8)变动事件,当底层DOM结构发生变化时触发。

(9)变动名称事件,当元素或属性名变动时触发。此类事件已经被废弃,没有任何浏览器实现它们。

五、性能和内存

1.事件委托

对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click事件会一直冒泡到document层次。也就是说,我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。以下面的HTML代码为例:

<ul id=”myLinks”>

<li id=”goSomewhere”>Go somewhere</li>

<li id=”doSomething”>Do something</li>

<li id=”sayHi”>Say hi</li>

</ul>

其中包含3个被单击后会执行操作的列表项。按照传统做法,需要像下面这样为它们添加3个事件处理程序。

var item1=document.getElementById("goSomewhere");

var item2=document.getElementById("doSomething");

var item3=document.getElementById("sayHi");

EventUtil.addHandler(item1,"click",function(event){

location.href="http://www.wrox.com";

});

EventUtil.addHandler(item2,"click",function(event){

document.title="I changed the document's title";

});

EventUtil.addHandler(item3,"click",function(event){

alert("hi");

});

如果在一个复杂的web应用程序中,对所有可单击的元素都采用这种方式,那么结果就会有数不清的代码用于添加事件处理程序。此时,可以利用事件委托技术解决这个问题。使用事件委托,只需在DOM树中尽量最高的层次上添加一个事件处理程序,如下面的例子。

var list=document.getElementById("myLinks");

EventUtil.addHandler(list,"click",function(event){

event=EventUtil.getEvent(event);

var target=EventUtil.getTarget(event);

switch(target.id){

case "doSomething";

document.title="I changed the document's title";

break;

case "goSomewhere";

location.href="http://www.wrox.com";

break;

case "sayHi";

alert("hi");

break;

}

});

2.移除事件处理程序

每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的javascript代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立的链接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那些过时不用的“空事件处理程序”,也是造成Web应用程序内存与性能问题的主要原因。

在两种情况下,可能会造成上述问题:第一种情况就是从文档中移除带有事件处理程序的元素时。第二种情况就是卸载页面的时候。

六、模拟事件

1.DOM中的事件模拟

可以在document对象上使用createEvent()方法创建event对象。这个方法接收一个参数,即表示要创建的事件类型的字符串。在DOM2级中所有这些字符串都是使用英文复数形式,而在DOM3级中变成了单数。

(1)模拟鼠标事件

创建新的鼠标事件对象并为其指定必要的信息就可以模拟鼠标事件。创建鼠标事件对象的方法是为createEvent()传入字符串“MouseEvents”。返回的对象有一个名为initMouseEvent()方法,用于指定与该鼠标事件有关的信息。这个方法接收15个参数。下面, 我们通过一个例子了解如何模拟对按钮的单击事件:

var btn=document.getElementById(“myBtn”);

// 创建事件对象

var event = document.createEvent(“MouseEvents”);

//初始化事件对象

event.initMouseEvent(“click”,true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null);

//触发事件

btn.dispatchEvent(event);

(2)模拟键盘事件

DOM3级规定,调用createEvent()并传入“keyboardEvent”就可以创建一个键盘事件。返回的事件对象会包含一个initKeyEvent()方法。

由于DOM3级不倡导使用keypress事件,因此只能利用这种技术来模拟keydown和keyup事件。

var textbox=document.getElementyById(“myTextbox”),event;

//以DOM3级方式创建事件对象

if (document.implementation.hasFeature(“KeyboardEvents”,”3.0”)){

event=document.createEvent(“KeyboardEvent”);

//初始化事件对象

event.initKeyboardEvent(“keydown”,true,true,document.defaultView,”a”,0,”shift”,0);

}

//触发事件

textbox.dispatchEvent(event);

这个例子模拟的是按住shift键的同时又按下A键。

在其他浏览器中,则需要创建一个通用的事件,然后再向事件对象中添加键盘事件特有的信息。例如:

var textbox=document.getElementById(“myTextbox”);

//创建事件对象

var event=document.createEvent(“Events”);

//初始化事件对象

event.initEven(type,bubbles,cancelable);

event.view=document.defaultView’

event.altKey=false;

event.ctrlKey=false;

event.shiftKey=false;

event.metaKey=false;

event.keyCode=65;

event.charCode=65;

//触发事件

textbox.dispatchEvent(event);

3.模拟其他事件

虽然鼠标事件和键盘事件是在浏览器中最经常模拟的事件,但有时候同样需要模拟变动事件和HTML事件。要模拟变动事件,可以使用createEvent(“MutationEvents”)创建一个包含initMutationEvent()方法的变动事件对象。例如:

var event=document.createEven(“MutationEvents”) ;

event.initMutationEvent(“DOMNodeInserted”,true,false,someNode,””,””,””,0);

target.dispatchEvent(event);

以上代码模拟了DOMNodeInserted事件。其他变动事件可以按照这个样子来模拟,只要改一改参数。

4.自定义DOM事件

自定义事件不是由DOM原生触发的,它的目的是让开发人员创建自己的事件。要创建新的定义事件,可以调用createEvent(“CustomEvent”)。返回的对象有一个名为initCustomEvent()的方法。

2.IE中的事件模拟

调用document.createEventObject()方法可以在IE中创建event对象。这个方法不接受参数,结果会返回一个通用的event对象。然后,手工为这个对象添加所有必要的信息。最后一步就是在目标上调用fireEvent()方法,这个方法接受两个参数:事件处理程序的名称和event对象。在调用fireEvent()方法时,会自动为event对象添加srcElement和type属性;其他属性则都必须通过手工添加。