13.2事件处理程序
一、什么是事件处理程序?
- 响应某个事件的函数就叫做事件处理程序(或事件侦听器)。【即监听这个事件的函数。】
- 事件处理程序的名字:以"on"开头,因此 click 事件的事件处理程序就是 onclick,load 事件的事件处理程序就是 onload。
- 为事件指定处理程序的方式有好几种。
二、HTML事件处理程序(高耦合,许多开发人员摒弃这种做法)
1.某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的 HTML 特性来指定【比如click事件的事件处理程序onclick,可以直接作为HTML特性写入元素中】。这个特性的值应该是能够执行的 JavaScript 代码。例如,要在按钮被单击时执行一些 JavaScript,可以像下面 这样编写代码:
<input type="button" value="Click Me" onclick="alert('Clicked')" />
-
由于这个属性值是 JavaScript,因此不能在其中使用未经转义的 HTML 语法字符, 例如和号(&)、双引号("")、小于号(<)或大于号(>)。为了避免使用 HTML 实体,这里使用了单引号。如果想要使用双引号,那么就要将代码改写成如下所示:
//把双引号转译成" <input type = "button" value = "Click Me" onclick = "alert("Clicked")" />
-
在 HTML 中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本,如下面的例子所示:
这个函数是在一个独立的<script>
元素 中定义的**,当然也可以被包含在一个外部文件中**。事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。 -
这样指定事件处理程序具有一些独到之处。首先,这样会创建一个封装着元素属性值的函数。这个函数中有一个局部变量 event,也就是事件对象(本章稍后讨论):
<!-- 输出 "click" --> <input type="button" value="Click Me" onclick="alert(event.type)">
- 通过 event 变量,可以直接访问事件对象,你不用自己定义它,也不用从函数的参数列表中读取。在这个函数内部,this 值等于事件的目标元素,例如:【注意此时,this与event不同,this是目标元素,event是事件对象】
<!-- 输出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(this.value)">
-
关于这个动态创建的函数,另一个有意思的地方是它扩展作用域的方式。在这个函数内部,可以像访问局部变量一样访问 document 及该元素本身的成员。这个函数使用 with 像下面这样扩展作用域:
如此一来,事件处理程序要访问自己的属性就简单多了【和上面的with原理一样】。下面这行代码与前面的例子效果相同:
-
如果当前元素是一个表单输入元素,则作用域中还会包含访问表单元素(父元素)的入口,这个函数就变成了如下所示:
实际上,这样扩展作用域的方式,无非就是想让事件处理程序无需引用表单元素就能访问其他表单字段。例如:
在这个例子中,单击按钮会显示文本框中的文本。值得注意的是,这里直接引用了 username 元素。 -
在 HTML 中指定事件处理程序有两个缺点:
首先,存在一个时差问题。当HTML元素已经加载完,但当时的事件处理程序有可能尚不具备执行条件,此时用户操作元素想触发事件,就会引发错误。
为此,很多 HTML 事件处理程序都会被封装在一个 try-catch 块中,以便错误不会浮出水面,如下面的例子所示:<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">
这样,如果在 showMessage()函数有定义之前单击了按钮,用户将不会看到 JavaScript 错误,因为在浏览器有机会处理错误之前,错误就被捕获了。
-
另一个缺点是,这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。不同 JavaScript 引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。
-
通过 HTML 指定事件处理程序的最后一个缺点****是 HTML 与 JavaScript 代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML 代码和 JavaScript 代码。而这正是许多开发人员摒弃 HTML 事件处理程序,转而使用 JavaScript 指定事件处理程序的原因所在。
三、DOM0 级事件处理程序
- 通过 JavaScript 指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。
-
优点:原因一是简单,二是具有跨浏览器的优势。
-
要使用 JavaScript 指定事件处理程序,首先必须取得一个要操作的对象的引用。
-
每个元素(包括 window 和 document)都有自己的事件处理程序属性,这些属性通常全部小写, 例如 onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序,如下所示:
var btn = document.getElementById("myBtn"); btn.onclick = function(){ //将元素的事件处理程序属性onclick的属性值,设置为一个函数 alert("Clicked"); };
但要注意,在这些代码运行以前不会指定事件处理程序,因此如果这些代码在页面中位于按钮后面,就有可能在一段时间内怎么单击都没有反应。
-
使用 DOM0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的 this 引用当前元素。来看一个例子。
不仅仅是 ID,实际上可以在事件处理程序中通过 this 访问元素的任何属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理
。 -
也可以删除通过 DOM0 级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设置为 null 即可:
btn.onclick = null; //删除事件处理程序
将事件处理程序设置为 null 之后,再单击按钮将不会有任何动作发生。
如果你使用 HTML 指定事件处理程序,将相应的属性设置为 null,也可以删除 以这种方式指定的事件处理程序。
四、DOM2 级事件处理程序
addEventListener()
和removeEventListener()
:“DOM2 级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作。
-
所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。
-
要在按钮上为click事件添加事件处理程序,可以使用下列代码:
上面的代码为一个按钮添加了 onclick 事件处理程序,而且该事件会在冒泡阶段被触发(因为最 后一个参数是 false)。 -
使用 DOM2 级方法添加事件处理程序的主要好处:可以添加多个事件处理程序。来看下面的例子。
这里为按钮添加了两个事件处理程序。这两个事件处理程序会按照添加它们的顺序触发,因此首先会显示元素的 ID,其次会显示"Hello world!"消息。【DOM0 级的方法(例如:btn.onclick = function(){})添加第二次会覆盖第一次的指定。】 -
通过 addEventListener()添加的事件处理程序
只能使用 removeEventListener()来移除;
移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过 addEventListener()添加的匿名函数将无法移除
,如下面的例子所示。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
//这里省略了其他代码
btn.removeEventListener("click", function(){ //没有用!
alert(this.id);
}, false);
在这个例子中,我们使用 addEventListener()添加了一个事件处理程序。虽然调用 removeEventListener()时看似使用了相同的参数,但实际上,第二个参数与传入 addEventListener()中 的那一个是完全不同的函数。而传入 removeEventListener()中的事件处理程序函数必须与传入 addEventListener()中的相同,如下面的例子所示。
重写后的这个例子没有问题,是因为在 addEventListener()和 removeEventListener()中使用了相同的函数。
- 大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段【第三个参数为false】,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。
五、IE事件处理程序
attachEvent()
和detachEvent()
方法: IE 实现了与 DOM 中类似的两个方法。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。
-
由于 IE8 及更早版本只支持事件冒泡,所以通过 attachEvent()添加的事件处理程序都会被添加到冒泡阶段【省去了DOM2中定义的方法中接收的第三个参数】。
-
要使用
attachEvent()
为按钮添加
一个事件处理程序,可以使用以下代码。
注意,attachEvent()的第一个参数是"onclick",而非 DOM 的 addEventListener()方法中 的"click"。 -
在 IE 中使用 attachEvent()与使用 DOM0 级方法的主要区别:在于事件处理程序的作用域。在使 用 DOM0 级方法的情况下,事件处理程序会在其所属元素的作用域内运行【this指向所属元素】;在使用 attachEvent()方 法的情况下,事件处理程序会在全局作用域中运行,因此 this 等于 window。来看下面的例子。
在编写跨浏览器的代码时,牢记这一区别非常重要。 -
与 addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。来看下面的例子。
这里调用了两次 attachEvent(),为同一个按钮添加了两个不同的事件处理程序。不过,与 DOM 方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。单击这个例子中的按钮,首先看到的是"Hello world!",然后才是"Clicked"。 -
使用 attachEvent()添加的事件可以通过
detachEvent()
来移除,条件是必须提供相同的参数。与 DOM 方法一样,这也意味着添加的匿名函数将不能被移除。不过,只要能够将对相同函数的引用传给 detachEvent(),就可以移除相应的事件处理程序。例如:
-
【与DOM方法的不同:事件第一个参数不同,一个是事件名一个是事件处理程序名称;this指向不同,一个是元素本身一个指向window。多次调用执行顺序不同。】
六、跨浏览器的事件处理程序
为了以跨浏览器的方式处理事件,不少开发人员会使用能够隔离浏览器差异的 JavaScript 库,还有一些开发人员会自己开发最合适的事件处理的方法。自己编写代码其实也不难,只要恰当地使用能力检测即可(能力检测在第 9 章介绍过)。要保证处理事件的代码能在大多数浏览器下一致地运行,只需关注冒泡阶段。
- 创建
addHandler()
方法:第一个要创建的方法是 addHandler(),它的职责是视情况分别使用 DOM0 级方法、DOM2 级方 法或 IE 方法来添加
事件。
- 这个方法属于一个名叫 EventUtil 的对象,本书将使用这个对象来处理浏览器间的差异。
- addHandler()方法接受 3 个参数:要操作的元素、事件名称和事件处理程序函数。
- removeHandler()方法:与 addHandler()对应的方法是 removeHandler(),它也接受相同的参数。这个方法的职责是移除之前添加的事件处理程序——无论该事件处理程序是采取什么方式添加到元素中的,如果其他方法无效,默认采用 DOM0 级方法。
- EventUtil 的用法如下所示。
- 可以像下面这样使用 EventUtil 对象:
- addHandler()和 removeHandler()没有考虑到所有的浏览器问题,例如在 IE 中的作用域问题【this指向问题没有解决】。 不过**,使用它们添加和移除事件处理程序还是足够了**。此外还要注意,DOM0 级对每个事件只支持一 个事件处理程序。好在,只支持 DOM0 级的浏览器已经没有那么多了,因此这对你而言应该不是什么问题。