找回密码
 注册

QQ登录

只需一步,快速开始

查看: 12909|回复: 9

Pro JavaScript Techniques第六章: 事件

[复制链接]
发表于 2007-5-14 11:52:03 | 显示全部楼层 |阅读模式
Pro Javascript Techniques翻译连载:说明和目录


  非侵入的DOM脚本最重要的一个方面就是使用动态绑定的事件。编写可用性良好的JavaScript代码的最终目的是,不论用户使用何种浏览器和系统平台,页面总能够正常工作。为了达到这一点,你需要设定想要使用的一系列的功能,并排除掉任何不支持它们的浏览器。对于不支持的浏览器,给它们一个较少交互性但仍然功能完备的网站版本。以这种方式编写JavaScript与HTML交互的好处包括更干净的代码、更具可访问性的页面和更好的用户交互。这一切都依靠使用DOM事件来改善web应用程序中的交互来完成。
  JavaScript里事件的概念经过多年来的发展,达到了现在所处的可信赖的、半可用的水平。可喜的是,由于事件的通用的相似性的存在,你可以开发一些优秀的工具来帮且你构建功能强大的、编码清晰的web应用程序。
  在这一章里,我将首先介绍JavaScript里事件是怎样工作的,并将它与其它语言里的事件进行一些比较。然后你将看到事件模型给你提供了些什么以及怎样最好地控制它。论述过向DOM元素绑定事件和可用的不同的事件类型以后,作为结尾,我将展示怎样将一些有效的非侵入的脚本技术整合到任何网页中。
 楼主| 发表于 2007-5-14 11:53:37 | 显示全部楼层
JavaScript事件简介

  审视任何JavaScript代码的核心,你会发现正是事件是把所有东西融合在一些。在一个设计良好的JavaScript应用程序里,你将拥有数据源和它的视觉的表示(在HTML DOM内部)。为了同步这两个方面,你必须监视用户的交互动作并试图相应地更新用户界面。DOM和JavaScript事件的结合是任何现代web应用程序赖以工作的至关重要的组合。

  异步事件vs.线程

  JavaScript里的事件系统是相当独特的。它完全地异步工作而根本不使用线程。这意味着你程序中的所有代码将依赖于其它的动作——比如用户的点击或者页面的加载——来触发。
  线程化的程序设计与异步的程序设计根本的不同点在于你怎样等待事情发生。在线程化的程序里,你需要不停地反复检查条件是否满足了。而在异步程序里你只须简单地通过事件句柄注册一个回调函数,一旦事件发生,句柄就会通过执行回调函数来让你知道。我们来探索一下假如使用线程JavaScript程序将会怎么编写和实际使用异步回调函数JavaScript又是怎么编写的。

  JavaScript线程

  按目前的情况来看,JavaScript线程并不存在。你最多是使用setTimeout回调函数来模拟,但即使是那样,也并不理想。程序6-1中所示是一段假想的线程化的JavaScript代码,在其中你正在等待,直到页面完成加载。如果JavaScript真是一个线程化语言的语言,你将不得不做那样的事。

  程序6-3. 模拟线程的JavaScript伪码

  1. // 注意:这段代码不能生效!
  2. // 在页面加载之前,持续地检查
  3. while ( ! window.loaded() ) { }

  4. //页面现在已经加载了,于是开始做些事情
  5. document.getElementById("body").style.border = "1px solid #000";
复制代码

  如你所见,这段代码里有一个循环,一直在检查window.loaded()是否返回true。且不说window对象根本没有loaded()这个函数,那样的循环也决不会在JavaScript中起作用。这是因为JavaScript中的循环是阻塞式的(也就是说它们运行完成之前别的什么事都不会发生)。假如JavaScript能够处理线程,你看到的情形将如图6-1所示。在图中,代码中的while循环持续地检查window是否已经加载。
6-1.gif
  图6-1. 如果JavaScript能处理线程你将会看到什么

  在实际的情况里,因为while循环持续地运行并阻断了应用程序的正常流程,true值永远不可到达。结果是用户的浏览器将会停止响应并可能崩溃。由此可知,如果有任何人声称在JavaScript里用while循环等待动作能够成功,他要么是说着玩,要么是迷糊得厉害。

  异步回调函数

  使用线程不断检查更新的替代方案是使用异步的回调,这正是JavaScript所使用的。直白地说,你告诉一个DOM元素,当指定的事件发生,你想要一个函数被调用以处理它。这意味着你只提供一个对希望执行的代码的引用,而浏览器处理所有的细节。程序6-2展示了使用事件句柄和回调函数的一段简单的代码。你会看到在JavaScript里把一个函数绑定到事件句柄(window.onload)上所需要的实际的代码。一但页面加载完成,window.onload就会被调用。其它通常的事件如click,mousemove和submit的情形也是这样。

  程序6-2. JavaScript里的异步回调

  1. //注册一个页面加载完成时被调用的函数
  2. window.onload = loaded;

  3. //页面加载完成时被调用的函数
  4. function loaded() {
  5.         //页面现己加载完成了,于是干点事情
  6.         document.getElementById("body").style.border = "1px solid #000";
  7. }
复制代码

  将程序6-2的代码与6-1中的进行比较,你会看到显著的不同。唯一被立即执行的代码是将事件句柄(loaded函数)向事件监听器(onload属性)的绑定。一旦页面完全加载,浏览器将调用与window.onload相关联的函数并执行它。JavaScript代码的流程如图6-2所示。图中展示了在JavaScript中使用回调函数来等待页面加载的一个图示。因为实际上不可能等待事情的发生,你将一个回调函数(loaded)注册到页面加载完成时会被调用的句柄(window.onload)上。
6-2.gif
  图6-2. 使用回调函数等待页面加载的示意图

  我们的简单的事件监听器和处理程序还没有立即显现的一个问题是,取决于事件类型和元素在DOM中位置的不同,事件会变得多样化并能以不同的方式来处理。下一节我们将看到事件的两个阶段及其不同点。

  事件的阶段

  JavaScript事件分为两个阶段执行:捕获(capturing)和冒泡(bubbling)。这意味着当事件从一个元素触发时(比如,用户点击一个链接导致click事件被触发),哪些元素允许处理它、以什么顺序处理它,变得多样化了。我们来看图6-3中的一个执行顺序的例子。图中说明了当用户点击页面中的第一个<a>元素时,哪些事件句柄以什么顺序被触发。
6-3.gif
  图6-3. 事件处理的两个阶段

  从这个简单的点击链接的例子里,你们可以看到事件的执行顺序。假设用户点击了一个<a>元素,文档的click句柄首先被触发,然后是<body>的句柄,然后是<div>的,等等,一直下行到<a>元素;这称为捕获阶段。此阶段完成以后,它又再次沿着树往上爬,<li>,<ul>,<div>,<body>,以及文档的事件句柄依次全部被触发。
  为什么事件处理会以这种方式建立有着很特别的原因,它工作得也非常好。假设你想每一个<li>元素在用户把鼠标移到上面时会改变背景颜色,当鼠标移开时又变回来(这是许多菜单的一般需要),程序6-3里的代码可以确切地做到这一点。

  程序6-3. 带鼠标悬停效果的标签导航方案

  1. //查找所有的<li>元素,并附以事件处理函数
  2. var li = document.getElementsByTagName("li");
  3. for ( var i = 0; i < li.length; i++ ) {

  4.         //为<li>元素附加mouseover事件处理函数,
  5.         //用来将元素的背景色改为蓝色
  6.         li[i].onmouseover = function() {
  7.                 this.style.backgroundColor = 'blue';
  8.         };

  9.         //为<li>元素附加mouseout事件处理函数,
  10.         //用来将元素的背景色改回缺省的白色
  11.         li[i].onmouseout = function() {
  12.                 this.style.backgroundColor = 'white';
  13.         };
  14. }
复制代码

  这些代码会确实如你所设想的那样运作:鼠标移到<li>元素上,它的背景色会改变,把鼠标移开,颜色又将还愿。但是,你可能没有意识到的是,当你每次把鼠标移到<li>的时候你实际上切换了两个元素。因为<li>元素包含<a>元素,你的鼠标同样滑过了它,而不仅仅是<li>元素。我们来看看事件调用的精确的流程:
  1. <li> mouseover: 鼠标到了<li>元素上
  2. <li> mouseout: 鼠标从<li>移到了它所包含的<a>元素
  3. <a> mouseover: 鼠标现在到了<a>元素上
  4. <li> mouseover: <a>的mousever事件向上冒泡成为<li>的mouseover
  从事件调用的方式上你可能已经觉察到,你完全忽略了事件的捕获阶段;不用担心,我可没忘记它。你绑定事件监听器的方式是古老的"传统"方式:设置元素的onevent属性;它只支持事件冒泡,不支持捕获。事件的这一绑定方式及其它方式,将在下一主题论述。
  除了事件调用的奇怪的顺序以外,你可能还注意到了两个意外的动作:鼠标移出<li>元素和<a>向<li>的mouseover冒泡。我们来仔细地看看。
  第一个mouseover事件发生,因为如浏览器认为,你离开了父级<li>元素的范围,进入了另一个元素。这是因为当前位于最上层的元素(正如<a>相对于其父元素<li>)将会接收到鼠标即时的焦点。
  <a>的mouseover向<li>元素的冒泡最终成就了我们那一段代码的优美。因为你实际上没有绑定任何种类的监听器给<a>元素,事件于是简单地沿着DOM上行,寻找另一个正在监听的元素。冒泡过程中它所遇到的第一个元素是<li>元素,恰巧监听着鼠标移入事件(这也正好是你想要的)。
  你需要考虑的是,要是你确实给<a>元素的mouseover绑定了事件处理程序呢?有什么方法可以停止事件的冒泡吗?这是我将要论述的另一个重要的主题。

[[i] 本帖最后由 mozart0 于 2007-5-14 11:56 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-14 11:53:41 | 显示全部楼层
事件的一般特性

  JavaScript事件很好一面是,它们有着一些相对一致的特性,给予你开发时的更多的能力和控制。最简单和最古老的概念是事件对象,它给你一系列的元数据和上下文相关的函数,允许你处理诸如鼠标事件和键盘按键事件等。另外,有一些函数用来修改事件的通常的捕获/冒泡流程。深入学习这些特性可以让你事半功倍。

  事件对象

  事件处理函数的一个标准功能是以某种方式访问包含当前事件的上下文信息的事件对象。这一对象在特定的事件中充当着非常有用的资源。比如,当处理键盘按下的事件时,你可以访问事件对象的keyCode属性,以得知被按下的是哪的键。在附录B中可以找到关于事件对象的更详细的说明。
  然而,事件对象棘手之处在于,IE的实现与W3C的规范并不相同。IE有一个单独的全局事件对象(可以可靠地通过全局属性window.event访问),而其它的每一种浏览器都把事件对象作为单个的参数传递给事件处理函数。可靠地使用事件对象的一个例子见程序6-4,代码修改一个普通的<textarea>元素,使其行为发生了改变。典型地,用户可以在<textarea>里按下回车键,产生一个换行符。但是假如你不希望那样做呢?函数正是提供了这一功能。

  程序6-4. 使用DOM事件重写功能

  1. //找到页面里的第一个<textarea>并为它绑定kerpress监听器
  2. document.getElementsByTagName("textarea")[0].onkeypress = function(e){
  3.         //如果不存在事件对象,就抓取那个全局的(ie only)
  4.         e = e || window.event;

  5.         //如果按下了回车键,返回false(导致它什么也不干)
  6.         return e.keyCode != 13;
  7. };
复制代码

  事件对象包含了大量的属性和函数,且它们的命名与行为在浏览器之间各不相同。我不想现在就进入那些细节,但是我强烈建议你阅读附录B,那是所有的事件对象属性的一个的列表,包括使用方法以及实际使用中的例子。

  this关键字

  this关键字(见第二章)提供了一种在函数作用域中的访问当前对象的方式。现代浏览器使用this关键字给所有的事件处理函数提供上下文信息。它们中只有一部分(而且只有部分方法)良好地运行,将它设为当前对象;这将很快地被深入讨论到。例如,在程序6-5中,我可以利用这一事实,只建立一个通用的函数来处理所有点击而通过this关键来确定作用于哪一个元素,它将如预期地工作。

  程序6-5. 点击时改变<li>元素的背景色

  1. //查看所有的<li>元素并给每一个绑定click处理函数
  2. var li = document.getElementsByTagName("li");
  3. for ( var i = 0; i < li.length; i++ ) {
  4.         li[i].onclick = handleClick;
  5. }

  6. //click处理函数,调用时改变特定元素的前景色和背景色
  7. function handleClick() {
  8.         this.style.backgroundColor = "blue";
  9.         this.style.color = "white";
  10. }
复制代码

  this关键字的确只是为了方便而设的,但我想你会发现,当使用它的属性,将会极大地降低你的JavaScript代码的复杂性。在本书中,我将试图使用this关键字编写所有的事件相关的代码。

  取消事件冒泡

  知道了事件的捕获/冒泡怎样工作以后,我们再来探讨怎样控制它。前面的例子里引入的一个很重要的问题是,如果你想要一个事件只在其目标上而不在基父级元素上出现,你处配办法停止它。阻止事件冒泡的过程将导致出现如图6-4所示的情形,在其中被第一个<a>元素捕获的事件的后续的冒泡被取消。
6-4.gif
  图6-4. 第一个<a>元素所捕获的事件被取消的结果

  停止事件的冒泡(或捕获)被证明在复杂的应用程序中是极其有用的。不幸的是,IE提供了一种与所有其它浏览器不同的方式来阻止事件冒泡。程序6-6是一个通用的取消事件冒泡的函数。该函数接受单个参数:传递到事件处理程序的事件对象。该函数处理取消事件冒泡的两种方式:标准的W3C方式和非标准的IE方式。

  程序6-6. 停止事件冒泡的通用函数

  1. function stopBubble(e) {
  2.         //如果提供了事件对象,则这是一个非IE浏览器
  3.         if ( e && e.stopPropagation )
  4.                 //因此它支持W3C的stopPropagation()方法
  5.                 e.stopPropagation();
  6.         else
  7.                 //否则,我们需要使用IE的方式来取消事件冒泡
  8.                 window.event.cancelBubble = true;
  9. }
复制代码

  现在你可能想知道的是,什么时候我想要阻止事件冒泡?老实说,多数时间里你可能从来不需担心这个。当你开始开发动态的应用程序(尤其是需要处理键盘和鼠标事件)时,这一需求才会变得突出。
  程序6-7展示了一个简明的代码片段:为你鼠标悬停的当前元素加上红色边框。如果不阻止事件冒泡,每一次你把鼠标移动到一个元素时,该元素及其所有的父级元素都将有一个并非我们想要的红色的边框。

  程序6-7. 使用stopBubble()创建一系列交互式的元素

  1. //查找并遍历DOM中的所有元素
  2. var all = document.getElementsByTagName("*");
  3. for ( var i = 0; i < all.length; i++ ) {

  4.         //监视用户何时把鼠标移到元素上,
  5.         //为该元素添加红色边框
  6.         all[i].onmouseover = function(e) {
  7.                 this.style.border = "1px solid red";
  8.                 stopBubble( e );
  9.         };

  10.         //监视用户何时把鼠标移出元素,
  11.         //删除我们所添加的红色边框
  12.         all[i].onmouseout = function(e) {
  13.                 this.style.border = "0px";
  14.                 stopBubble( e );
  15.         };

  16. }
复制代码


  拥有阻止事件冒泡的能力,你就能对事件到达哪个元素并进行处理有了完全的控制。这是开发动态的web应用程序所需的一个非常工具。最后一点,取消浏览器的默认动作,这允许你完全改写浏览器的行为并实现新的功能以替代之。

  改写浏览器的默认动作

  对于发生的大多数事件,浏览器有一些总会发生的默认动作。比如说,点击一个<a>元素将会把你带到它所关联的网页;这是浏览器的一个默认动作。这一动作总是在事件的捕获和冒泡阶段都完成以后发生,如图6-5所示。该示例说明了用户在页面点击<a>元素的结果。事件起初经历在DOM中的捕获和冒泡阶段(如前所述)。然而,一旦事件完成了其旅程,浏览器将试图执行该事件及元素的默认动作。在这里也就是访问链接的网页。
6-5.gif
  图6-5. 事件的完整生命周期

  默认动作可以概括为浏览器所执行的你没有明确指定的操作。下面是特定事件发生时几种不同类型的默认动作:
  a. 点击一个<a>元素将会跳转到元素的href属性指定的URL。
  b. 按下Ctrl+S键,浏览器将试图保存当前网页。
  c. 提交一个HTML<form>元素将从指定URL查询数据并将浏览器重定向到该地址。
  d. 移动鼠标到带有alt或title属性(取决于不同的浏览器)的<img>元素将导致出现一个工具提示,提供该<img>元素的描述。
  即使你阻止了事件的冒泡或者根本没有设置事件处理函数,上述事件也会被浏览器执行。这在你的脚本中会导致显著的问题。如果你想让你的表单有不同的行为呢?或者如果你想要<a>元素以不同于它们本来目的的方式运作呢?因为取消事件冒泡不足以阻止默认行为,你需要一些特别的代码在直接处理它们。跟取消事件冒泡一样,有两种方式来阻止默认动作的发生:IE特有的方式和W3C方式。两种方式见于程序6-8。其中展示的函数接受单个参数:传递到事件处理函数的事件对象,使用方法如return stopDefault(e);——当你的处理函数也需要返回false(这是stopDefault为你所返回的)时。

  程序6-8. 阻止浏览器默认动作发生的通用函数

  1. function stopDefault( e ) {
  2.         //阻止默认浏览器动作(W3C)
  3.         if ( e && e.preventDefault )
  4.                 e.preventDefault();
  5.         //IE中阻止函数器默认动作的方式
  6.         else
  7.                 window.event.returnValue = false;
  8.         return false;
  9. }
复制代码

  使用stopDefault函数,你现在可以阻止浏览器给出的任何默认动作。这允许你用脚本为用户编写出灵巧的交互,如程序6-9所示。此代码使一个页面内所有的链接在一个自包含的<iframe>中加载,而不是打开整个新的页面。这么做可以使你把用户保持在页面上,并可能给出更具交互性的体验。
  注意:在95%的情况下,阻止默认动作会生效。然而事情在你跨越浏览器时会变得棘手起来,因为阻止默认事件取决于浏览器(它们并不总能做对),尤其是当阻止文本输入框里的按键事件的动作和阻止iframe里的动作时;除了这些以外,应该还是足够健全的。

  程序6-9. 使用stopDefault()来改写浏览器功能
[code]
//假设页面中已经有一个ID为iframe的<iframe>元素
var iframe = document.getElementById("iframe");

//查找页面中的所有<a>元素
var a = document.getElementsByTagName("a");
for ( var i = 0; i < a.length; i++ ) {

        //为<a>元素绑定事件处理函数
        a[i].onclick = function(e) {
                //设置iframe的location
                iframe.src = this.href;

                //阻止浏览器访问<a>元素所指定的网页(默认动作)
                return stopDefault( e );
        };

}
[/code]
  改写默认事件绝对是共同组成了非侵入式脚本的DOM和事件的关键所在。在本章后面的"非侵入的DOM脚本"中我将立足于功能,更多地谈到这一点;当你实际地将事件处理函数绑定到DOM元素时,争论的要点出现了。事实上有三种绑定事件的方式,其中一些比另一些要好。下一节将会讨论它们。

[[i] 本帖最后由 mozart0 于 2007-5-14 11:58 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-14 11:53:44 | 显示全部楼层
绑定事件监听器

  怎样将事件处理程序绑定到元素是JavaScript里一直以来不断推进的追求。起初,浏览器强制用户将处理代码内联地写在HTML文档中。好在那一些技术已经变得远远过时了(说这是一件好事,是考虑到它与非侵入的DOM脚本里数据抽象的精神相悖)。
  当IE与NetScape激烈竞争的时候,它们各自开发出两个独立但又非常相似的注册事件的模型。最终NetScape的模型被修改成为W3C标准,而IE的则保持不变。
  于是乎,目前存在三种可用的注册事件的方式。传统方式是老式的内联附加事件处理函数方式的一个分支,但是它很可靠而并能一致地工作。另外两种是IE和W3C的注册事件的方式。最后,我将给出一套可靠的方法,开发者可以用它们来注册和注销事件而不需再担心底层是什么浏览器。

  传统绑定

  传统的绑定事件的方式是我在本章中到目前为止所一直使用的。它是到目前为止最简单最兼容的绑定事件处理程序的方式。使用这种方式时,你只需将函数作为一个属性附加到你想要监视的DOM元素上。6-10展示了使用传统方式绑定事件的一些例子。

  程序6-10. 使用传统的事件绑定方式附加事件

  1. //找到第一个<form>元素并为它附加“提交”事件处理函数
  2. document.getElementsByTagName("form")[0].onsubmit = function(e){
  3.         //阻止表单提交
  4.         eturn stopDefault( e );
  5. };

  6. //为文档的body元素附加一个按键事件处理函数
  7. document.body.onkeypress = myKeyPressHandler;

  8. //为页面的加载事件附加一个处理函数
  9. window.onload = function(){ … };
复制代码

  这一技术有一系列的优势和缺点,使用时必须注意。
  传统绑定的优势:
  a. 使用传统绑定的最大的好处在于它无比地简单和一致,也就是说在很大程度上它能保障无论使用什么浏览器都能生效。
  b. 当处理事件时,this关键字指向当前的元素,这一点是非常有用的(如程序6-5示范的那样)。
  传统绑定的缺点:
  a. 传统绑定只作用于事件冒泡,而非捕获和冒泡。
  b. 只能每次为一个元素绑定一个事件处理函数。当使用流行的window.onload属性时,这将会潜在地导致令人困惑的结果(因为它会覆盖其它的使用相同方法绑定的代码片段)。程序6-11展示了这一问题的一个实例,一个新的事件处理函数覆盖了原来的事件处理函数。
  c. event对象参数只在非IE浏览器上有效

程序6-11. 事件处理函数互相覆盖

  1. //绑定初始的load处理函数
  2. window.onload = myFirstHandler;

  3. //在某个地方,你所引用的其它库里,你的第一个处理函数被覆盖,
  4. //页面加载完成时只有mySecongHandler函数被调用
  5. window.onload = mySecondHandler;
复制代码

  懂得了盲目覆盖其它事件的可能性,你可能会选择只在可以信任所有其它的代码简单的情况下使用传统绑定。解决这一混乱情况的一种方式是使用浏览器提供的现代绑定方法。

  DOM绑定:W3C

  W3C的为DOM元素绑定事件处理函数的方法是这方面唯一真正的标准方式。除了IE,所有其它的现代浏览器都支持这一事件绑定的方式。
  附加新的处理函数的代码很简单。它作为每一个DOM元素的名为addEventListener的方法存在,接收3个参数:事件的名称(如click),处理事件的函数,以及一个来用使用或禁用事件捕获的布尔标志。程序6-12展示一个实际使用addEventListener的例子。

  程序6-12. 使用W3C方式绑定事件处理函数的示例代码片段

  1. //找到第一个<form>元素并为它附加“提交”事件处理函数
  2. document.getElementsByTagName("form")[0].addEventListener('submit',function(e){
  3.         //阻止表单提交
  4.         return stopDefault( e );
  5. }, false);

  6. //为文档的body元素附加一个按键事件处理函数
  7. document.body.addEventListener('keypress', myKeyPressHandler, false);

  8. //为页面的加载事件附加一个处理函数
  9. window.addEventListener('load', function(){ … }, false);
复制代码

  W3C绑定的优势:
  1. 这一方法同时支持事件处理的冒泡和捕获阶段。事件的阶段通过设置addEventListener的最后一个参数为false(指示冒泡)或true(指示捕获)来切换。
  2. 在事件处理函数内部,this关键字引用当前元素。
  3. 事件对象总是作为事件处理函数的第一个参数被提供。
  4. 你可以绑定任意多个函数到一个元素上,而不会覆盖先前所绑定的。
  W3C绑定的缺点
  1. 它在IE里面无效。你必须使用IE的attachEvent函数来代替。
  如果IE采用了W3C的方法来绑定事件处理函数,这一章将会比现在短得多,因为那将会不再需要讨论绑定事件的替代方法。然而,到目前为止,W3C的事件绑定方法仍然是最可理解和最易使用的。

  DOM绑定:IE

  在许多方面,IE的绑定事件的方式看起来跟W3C的非常相似。但是,当你触及细节的时候,它又在某些方面有着非常显著的不同。程序6-13是IE中绑定事件处理函数的一些例子。

程序6-13. 使用IE的方式绑定事件处理函数的示例

  1. //找到第一个<form>元素并为它附加“提交”事件处理函数
  2. document.getElementsByTagName("form")[0].attachEvent('onsubmit',function(){
  3.         //阻止表单提交
  4.         return stopDefault();
  5. });

  6. //为文档的body元素附加一个按键事件处理函数
  7. document.body.attachEvent('onkeypress', myKeyPressHandler);

  8. //为页面的加载事件附加一个处理函数
  9. window.attachEvent('onload', function(){ … });
复制代码

  IE绑定的优势
  1. 你可以绑定任意多个函数到一个元素上,而不会覆盖先前绑定的。
  IE绑定的缺点
  1. IE只支持事件的冒泡阶段。
  2. 事件监听函数内部的this关键字指向window对象,而非当前函数(这是IE的巨大败笔)。
  3. 事件对象只能从widnow.event得到。
  4. 事件名称必须形如onxxxx——比如,要使用"onclick"而不能只是"click"。
  5. 它只对IE有效。对于非IE平台,你必须使用W3C的addEventListener。
  相对其半标准的事件特性,IE事件绑定的实现是严重短缺的。鉴于它的许多不足之处,弥补方案必须继续存在以强制它合理的运转。然而,幸运的是,向DOM添加事件的通用的函数确实存在,它能极大的减轻我们的痛苦。

  addEvent和removeEvent

  2005年末,Peter-Paul Koch(http://quirksmode.org)发起了一个竞赛,向JavaScript代码编写者公开征求一对新的函数,addEvent和removeEvent,用来提供一个可靠的方式为DOM元素添加和删除事件。最终我以一段非常简练而又能足够好地运行的一段代码在其中获胜。但是,后来,其中一位裁判(Dean Edwards)给出了函数的另一个版本,远远地超越了我所编写的。它的实现使用传统的方式来附加事件处理函数,完成忽略现代方法。因此,它可以在大量的浏览器上运行,而仍然提供了必要的事件的优美性(比如this关键字和标准的事件对象)。程序6-14展示的一段示例代码很好的利用新的addEvent函数,使用了事件处理的所有的不同侧面,包括浏览器默认动作的阻止,正确的事件对象的引入,和正确的this关键字的引入。

程序6-14. 使用addEvent函数的示例代码片段

  1. //等待页面加载完成
  2. addEvent( window, "load", function(){

  3.         //监视用户的任何按键
  4.         addEvent( document.body, "keypress", function(e){
  5.                 //如果用户按下了Ctrl+Spance
  6.                 if ( e.keyCode == 32 && e.ctrlKey ) {
  7.                
  8.                         //显示我们的特别的表单
  9.                         this.getElementsByTagName("form")[0].style.display = 'block';
  10.                         //确保没有怪异的事情发生
  11.                         e.preventDefault();

  12.                 }
  13.         });

  14. });
复制代码

  addEvent函数提供了一个绝妙的简单而强大的方式来处理DOM事件。只要看看优势和不足,就可以明显地看出这一函数可以作为一致而可靠的方式来处理事件。程序6-15是完整的源代码,它能在所有的浏览器中运行,不泄露任何内存,处理了this关键字和事件对象,并标准化了事件对象。

  程序6-15. Dean Edwards编写的addEvent/removeEvent库

  1. // addEvent/removeEvent written by Dean Edwards, 2005
  2. // with input from Tino Zijdel
  3. // http://dean.edwards.name/weblog/2005/10/add-event/

  4. function addEvent(element, type, handler) {
  5.         //为每一个事件处理函数分派一个唯一的ID
  6.         if (!handler.$$guid) handler.$$guid = addEvent.guid++;

  7.         //为元素的事件类型创建一个哈希表
  8.         if (!element.events) element.events = {};

  9.         //为每一个"元素/事件"对创建一个事件处理程序的哈希表
  10.         var handlers = element.events[type];
  11.         if (!handlers) {
  12.                 handlers = element.events[type] = {};

  13.                 //存储存在的事件处理函数(如果有)
  14.                 if (element["on" + type]) {
  15.                         handlers[0] = element["on" + type];
  16.                 }

  17.         }

  18.         //将事件处理函数存入哈希表
  19.         handlers[handler.$$guid] = handler;

  20.         //指派一个全局的事件处理函数来做所有的工作
  21.         element["on" + type] = handleEvent;
  22. };

  23. //用来创建唯一的ID的计数器
  24. addEvent.guid = 1;

  25. function removeEvent(element, type, handler) {
  26.         //从哈希表中删除事件处理函数
  27.         if (element.events && element.events[type]) {
  28.                 delete element.events[type][handler.$$guid];
  29.         }
  30. };

  31. function handleEvent(event) {
  32.         var returnValue = true;

  33.         //抓获事件对象(IE使用全局事件对象)
  34.         event = event || fixEvent(window.event);

  35.         //取得事件处理函数的哈希表的引用
  36.         var handlers = this.events[event.type];

  37.         //执行每一个处理函数
  38.         for (var i in handlers) {
  39.                 this.$$handleEvent = handlers[i];
  40.                 if (this.$$handleEvent(event) === false) {
  41.                         returnValue = false;
  42.                 }
  43.         }

  44.         return returnValue;
  45. };

  46. //为IE的事件对象添加一些“缺失的”函数
  47. function fixEvent(event) {
  48.         //添加标准的W3C方法
  49.         event.preventDefault = fixEvent.preventDefault;
  50.         event.stopPropagation = fixEvent.stopPropagation;
  51.         return event;
  52. };

  53. fixEvent.preventDefault = function() {
  54.         this.returnValue = false;
  55. };

  56. fixEvent.stopPropagation = function() {
  57.         this.cancelBubble = true;
  58. };
复制代码

  addEvent函数的优势
  1. 它可以在所有浏览器上工作,甚至是很老的不被支持的浏览器
  2. this关键字对所有的绑定的函数可用,指向当前元素
  3. 浏览器特有的阻止浏览器默认动作和停止事件冒泡的函数都被统一了
  4. 不管是哪种浏览器,事件对象总是作为第一个参数传给处理函数
  addEvent函数的缺点
  1. 它只能工作于冒泡阶段(因为它在底层使用了传统事件绑定方法)
  考虑到addEvent/removeEvent函数是如此的强大,绝对没有理由不在你的代码中使用它们。在Dean的代码的基础上,添加一些诸如更好的事件对象标准化、事件触发、大量事件删除这类在通常的事件结构中原本极难的事情委实是轻而易举。

[[i] 本帖最后由 mozart0 于 2007-5-14 11:59 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-14 11:53:47 | 显示全部楼层
事件的类型

  JavaScript事件可以被归入几种不同的类别。最常用的类别可能是鼠标交互事件,然后是键盘和表单事件。下面的列表提供了web应用程序中存在并可被处理的不同各类的事件的粗略预览。参考附录A和附录B,可以得到大量的事件的实例。
  [i]鼠标事件: 分为两种,追踪鼠标当前位置的事件(mouseover,mouseout),和追踪鼠标在哪儿被点击的事件(mouseup,mousedown,click)。
  [i]键盘事件: 负责追踪键盘的按键何时以及在何种上下文中(比如说,追踪一个form元素内的按键相对于出现在整个页面的按键)被按下。与鼠标相似,三个事件用来追踪键盘:keyup,keydown,keypress。
  [i]UI事件: 用来追踪用户何时从页面的一部分转到另一部分。例如,使用它你能够可靠地知道用户何时开始在一个表单中输入。用来追踪这一点的两个事件是focus和blur(用于对象失去焦点时)。
  [i]表单事件: 直接与只发生于表单和表单输入元素上的交互相关。submit事件用来追踪表单何时提交;change事件监视用户向元素的输入;select事件当<select>元素被更新时触发。
  [i]加载和错误事件: 事件的最后一类是与页面本身有关的,关注页面的加载状态。它们被关联到何时用户第一次加载页面(load事件)和最终离开页面(unload和beforeunload事件)。另外,JavaScript错误使用error事件追踪,这给了你以独立处理错误的能力。
  记住这些大致的事件分类,我推荐你积极地查看附录A和附录B的材料,其中剖析了所有的常用的事件:它们怎样工作,在不同的浏览器中有着怎样的差别,并描述了使它们如你所希望的那样工作所需的所有复杂细节。

[[i] 本帖最后由 mozart0 于 2007-5-14 12:00 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-14 11:54:03 | 显示全部楼层
非侵入的DOM脚本

  到目前为止你所学到的每样东西都指向这一重要的目标:编写JavaScript代码使它与你的用户自然而非侵入地交互。这一脚本风格背后的驱动力量是,你可以将你的精力集中于编写良好的代码,它们运行于现代浏览器而在老的(不支持的)浏览器里则优雅地隐退。
  为实现这一点,你可以结合已经学到的三种技术来使一个应用程序以非侵入的形式被构造:
  1.应用程序中的所有功能都应经过验证。比如说,如果你希望访问HTML DOM,你需要验证它存在且具有你需要使用的所有的功能(如if(document&&document.getElementById))。这一技术在第二章讨论过。
  2.使用DOM来快速而一致地访问你文档中的元素。因为你已经知道浏览器支持DOM函数,你可以放心地简单的编写代码而无需hack或者拼凑。
  3.最后,使用DOM和你的addEvent函数动态地将所有事件绑定到文档中。不要让代码中出现这样的东西:<a href="#" onclick="doStuff();">...</a>。用非侵入编码的角度来看,这是非常不好的,如果JavaScript被关闭或者用户使用了不支持的老版本的浏览器,这些代码就是一堆垃圾。因为你只是将用户指向了没有意义的URL,它将不能对不支持你脚本功能的用户提供任何有效的交互。
  如果这还不够明显,你需要假设用户根本没有安装JavaScript,或者他的浏览器在某方面很低能。打开你的浏览器,访问你最喜欢的站点,划关闭JavaScript:它还能工作吗?禁用所有的css又如何呢?你还能导航到你想到的那个位置吗?最后,不使用鼠标能否访问你的站点?所有这些都应该是你的网站的最终目标的一部分。可喜的是,因为你已经建立起一个对于怎样编写真正有效的JavaScript代码的良好的认识,这一过渡的代价是可以忽略,通过最少的努力就能实现。

  假设JavaScript被禁用

  你应该达到的第一个目标是彻底删除HTML文档中所有的内联事件绑定。可以检查文档里几个经常出问题的方面:
  a. 如果你禁用了页面上的JavaScript,并点击任何/所有链接,它们能把你带到一个网页吗?开发者可能会频繁地使用形如href=""或href="#"的URL,这意味着它们运行一些额外的JavaScript伎俩来为用户的提供结果。
  b. 如果你禁用了JavaScript,你所有的表单都还能正常工作和提交吗?当使用<select>作为动态菜单时(那只能在JavaScript使能的情况下工作),通常就会出问题。
  遵从使用这些重要的训诫,你将得到对禁用了JavaScript和仍然使用不被支持的老浏览器的用户来说完全可以使用的网页。

  确保链接不依赖JavaScript

  因为用户可以在页面上执行所有的动作,你需要确保用户在任何动作执行之前都得到了恰当的通知。当Google发布它的Google Accelerator(遍历页面上的所有链接并为你缓存它们)时,用户发现它们的e-mail,帖子,和消息被不加明显提示地魔法般地删除了。这是因为开发者在它们的页面中放置了用来删除消息的链接,并通过弹出对话框(使用JavaScript)来确认删除。但是Google Accelerator完成忽略了那些弹出对话框,如它应该做的那样,终究访问了那些链接。
  这一情境是向你指引用于在网上传输所有的文档和文件的Http规范的一个巧妙的方式。最简单的情形里,GET请求产生于点击链接时,POST请求发生于提交表单时。这个规范的开头就说到,GET请求不应该产生破坏性的副作用(比如删除消息),这就是Google Accelerator自行其是的原因。可见问题并不是出在Google方面的不良的程序设计,而在于创建了那些链接的web应用程序的开发者方面。
  简单地说,你站点上的所有链接都应该是非破坏性的。如果通过点击链接你可以删除、编辑、或者修改任何用户拥有的数据,你或许应该使用表单来实现那一目的。

  监视CSS何时被禁用

  新老浏览器的交叉点是一个特别棘手的情况:浏览器太老而不能支持现代JavaScript技术但是又足够新可以支持CSS样式。一种流行的DHTML技术是起初将一个元素隐藏(通过设置display为none或者visibility为hidden),然后在用户进入页面时(使用JavaScript)使它逐渐显示。但是,如果用户禁用了JavaScript,他将永远看不到那个元素。程序6-16给了一个针对这一问题的解决方案。

  程序6-16. 提供一个即使JavaScript被禁用也不会失败的加载渐显技术

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  2. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head>
  5.         <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  6.         <!--一旦脚本运行,一个新的类将被附加到<html>元素上,可以藉此判断JavaScript是否可用-->
  7.         <script>document.documentElement.className = "js";</script>
  8.         <!--如果JavaScript可用,则隐藏稍后我们将渐显的文本块-->
  9.         <style>.js #fadein { display: none }</style>
  10. </head>
  11. <body>
  12.         <div id="fadein">Block of stuff to fade in…</div>
  13. </body>
  14. </html>
复制代码

  这一技术超越了简单的渐显DHTML。对web开发者来说,知道JavaScript是否可用并据此来应用样式是很有益的。

  事件的可访问性
  
  开发纯粹的非侵入的web应用程序时应该注意的最后一点是确保你的事件在不使用鼠标的情况下也能工作。做到这一点,你帮助了两类人:需要访问辅助的(有视觉缺陷的)用户,和不喜欢使用鼠标的人。某一天功夫从电脑上拔掉鼠标,坐下来学习怎样只使用键盘访问网站,这绝对是很有启发性的体验。
  为了确保你的JavaScript事件更易访问,任何时候你使用click、mouseover和mousemove时,你需要周密地考虑提供非鼠标绑定的替代方案。好在存在着快速补救这一情形的容易的方式:
  [i]click事件: 浏览器开发者方面的一个巧妙的举措是使得当Enter键被按下,click事件就会触发,完全省去了为这一事件提供替换方案的必要性。但是,应该注意的一点是,一些开发者喜欢为提交按钮绑定处理函数来监视用户何时提交一个网页。替代使用该事件,开发者应该绑定到form对象的submit事件上,这是一个明智而可靠的替代方案。
  [i]mouseover事件: 当使用键盘导航网页时,你实际是在把焦点移到不同的元素。通过同时附加事件处理函数到mouseover和focus事件上,你可以确保对键盘和鼠标用户都有一个相当的解决方案。
  [i]mouseout事件: 与用于mouseover事件的focus事件对应,blur事件在用户焦点移出一个元素时发生。你可以使用blur事件作为用键盘模拟mouseout事件的一种方式。
  学会了哪些事件对能够如你所想地运行,现在你可以重拾程序6-3,建立一个即使没有鼠标也能生效的类似鼠标悬停效果,如程序6-17所示。

  程序6-17. 为元素的成对的事件附加处理函数以改善网页的可访问性

  1. // 找到所有的<a>元素,以向其附加事件处理函数
  2. var li = document.getElementsByTagName("a");
  3. for ( var i = 0; i < a.length; i++ ) {

  4.         //为<a>元素附加mouseover和focus事件处理函数,
  5.         //当用户鼠标移到链接上或者使用键盘将移动焦点到链接上时,改变<a>的背景色为蓝色
  6.         a[i].onmouseover = a[i].onfocus = function() {
  7.                 this.style.backgroundColor = 'blue';
  8.         };

  9.         //为<a>元素附加mouseout和blur事件处理函数,
  10.         //当用户鼠标移出该链接或者使用键盘使链接失去焦点时,将<a>的背景色为改回白色
  11.         a[i].onmouseout = a[i].onblur = function() {
  12.                 this.style.backgroundColor = 'white';
  13.         };
  14. }
复制代码

  实际上,在典型的鼠标事件以外再添加处理键盘事件的能力也费不了多少事。即使没有别的用处,也有助于作为依赖键盘的用户更好的访问你的站点的一种方式,这对每个人来说都是件好事。

[[i] 本帖最后由 mozart0 于 2007-5-14 12:00 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-14 11:54:06 | 显示全部楼层
本章摘要

  学会了怎样穿行于DOM,为DOM元素绑定事件处理函数,并且了解了非侵入地编写JavaScript代码的益处之后,现在你已经可以开始着手对付一些更大的应用程序和更酷的效果了。
  在这一章,我以对JavaScript中事件怎样工作的介绍开头,并将它们与其它语言里的事件模型进行了比较。随后你看到了事件模型提供了什么信息以及怎样最好的控制它。然后我们探索了绑定事件到DOM元素的方法,以及可用的不同的事件类型。作为结尾,我介绍了怎样把有效的非侵入的脚本技术整合到任何网页中。
  接下来你将会看到怎样利用你刚刚学到的这些技术实现一些动态的交互。

[[i] 本帖最后由 mozart0 于 2007-5-14 12:01 编辑 ]
回复 支持 反对

使用道具 举报

发表于 2007-5-14 12:47:42 | 显示全部楼层
好贴,THX楼主,向楼主学习~~
回复 支持 反对

使用道具 举报

发表于 2007-8-1 10:41:04 | 显示全部楼层
楼主, 真是翻译的太好了, 我们正在翻译jQuery的资料: http://bbs.jquery.org.cn/viewthread.php?tid=578  欢迎指点呀.
回复 支持 反对

使用道具 举报

发表于 2007-11-16 14:42:30 | 显示全部楼层

addEvent的问题

addEvent还有一个重大的缺点就是:使用addEvent为这个元素添加某事件处理方法后,再直接在该元素上使用传统绑定某事件,就会导致前面注册的事件全部被覆盖!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|Archiver|手机版|blueidea.com ( 湘ICP备19000417号-2 )

GMT+8, 2021-10-17 04:54 , Processed in 0.068545 second(s), 12 queries , Gzip On, Memcache On.

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表