Javascript事件简介+取消默认动作

JavaScript事件简介

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

异步事件vs.线程

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

  JavaScript线程

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

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

复制内容到剪贴板
代码:
// 注意:这段代码不能生效!
// 在页面加载之前,持续地检查
while ( ! window.loaded() ) { }
//页面现在已经加载了,于是开始做些事情
document.getElementByIdx_x("body").style.border = "1px solid #000";

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



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

  异步回调函数

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

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

复制内容到剪贴板
代码:
//注册一个页面加载完成时被调用的函数
window.onload = loaded;
//页面加载完成时被调用的函数
function loaded() {
    //页面现己加载完成了,于是干点事情
    document.getElementByIdx_x("body").style.border = "1px solid #000";
}

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



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

事件的阶段

  JavaScript事件分为两个阶段执行:捕获(capturing)和冒泡(bubbling)。这意味着当事件从一个元素触发时(比如,用户点击一个链接导致click事件被触发),哪些元素允许处理它、以什么顺序处理它,变得多样化了。

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

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

复制内容到剪贴板
代码:
//查找所有的<li>元素,并附以事件处理函数
var li = document.getElementsByTagName_r("li");
for ( var i = 0; i < li.length; i++ ) {
    //为<li>元素附加mouseover事件处理函数,
    //用来将元素的背景色改为蓝色
    li[i].onmouseover = function() {
        this.style.backgroundColor = 'blue';
    };
    //为<li>元素附加mouseout事件处理函数,
    //用来将元素的背景色改回缺省的白色
    li[i].onmouseout = function() {
        this.style.backgroundColor = 'white';
    };
}

  这些代码会确实如你所设想的那样运作:鼠标移到<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绑定了事件处理程序呢?有什么方法可以停止事件的冒泡吗?这是我将要论述的另一个重要的主题。
 

事件的一般特性

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

事件对象

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

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

复制内容到剪贴板
代码:
//找到页面里的第一个<textarea>并为它绑定kerpress监听器
document.getElementsByTagName_r("textarea")[0].onkeypress = function(e){
    //如果不存在事件对象,就抓取那个全局的(ie only)
    e = e || window.event;
    //如果按下了回车键,返回false(导致它什么也不干)
    return e.keyCode != 13;
};

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

this关键字

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

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

复制内容到剪贴板
代码:
//查看所有的<li>元素并给每一个绑定click处理函数
var li = document.getElementsByTagName_r("li");
for ( var i = 0; i < li.length; i++ ) {
    li[i].onclick = handleClick;
}
//click处理函数,调用时改变特定元素的前景色和背景色
function handleClick() {
    this.style.backgroundColor = "blue";
    this.style.color = "white";
}

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

取消事件冒泡

  知道了事件的捕获/冒泡怎样工作以后,我们再来探讨怎样控制它。前面的例子里引入的一个很重要的问题是,如果你想要一个事件只在其目标上而不在基父级元素上出现,你处配办法停止它。



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

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

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

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

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

复制内容到剪贴板
代码:
//查找并遍历DOM中的所有元素
var all = document.getElementsByTagName_r("*");
for ( var i = 0; i < all.length; i++ ) {
    //监视用户何时把鼠标移到元素上,
    //为该元素添加红色边框
    all[i].onmouseover = function(e) {
        this.style.border = "1px solid red";
        stopBubble( e );
    };
    //监视用户何时把鼠标移出元素,
    //删除我们所添加的红色边框
    all[i].onmouseout = function(e) {
        this.style.border = "0px";
        stopBubble( e );
    };
}

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

改写浏览器的默认动作

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



  默认动作可以概括为浏览器所执行的你没有明确指定的操作。下面是特定事件发生时几种不同类型的默认动作:
  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. 阻止浏览器默认动作发生的通用函数

复制内容到剪贴板
代码:
function stopDefault( e ) {
    //阻止默认浏览器动作(W3C)
    if ( e && e.preventDefault )
        e.preventDefault();
    //IE中阻止函数器默认动作的方式
    else
        window.event.returnValue = false;
    return false;
}

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

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

//查找页面中的所有<a>元素
var a = document.getElementsByTagName_r("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元素时,争论的要点出现了。事实上有三种绑定事件的方式,其中一些比另一些要好。下一节将会讨论它们。
 

绑定事件监听器

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

传统绑定

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

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

复制内容到剪贴板
代码:
//找到第一个<form>元素并为它附加“提交”事件处理函数
document.getElementsByTagName_r("form")[0].onsubmit = function(e){
    //阻止表单提交
    eturn stopDefault( e );
};
//为文档的body元素附加一个按键事件处理函数
document.body.onkeypress = myKeyPressHandler;
//为页面的加载事件附加一个处理函数
window.onload = function(){ … };

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

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

复制内容到剪贴板
代码:
//绑定初始的load处理函数
window.onload = myFirstHandler;
//在某个地方,你所引用的其它库里,你的第一个处理函数被覆盖,
//页面加载完成时只有mySecongHandler函数被调用
window.onload = mySecondHandler;

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

DOM绑定:W3C

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

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

复制内容到剪贴板
代码:
//找到第一个<form>元素并为它附加“提交”事件处理函数
document.getElementsByTagName_r("form")[0].addEventListener('submit',function(e){
    //阻止表单提交
    return stopDefault( e );
}, false);
//为文档的body元素附加一个按键事件处理函数
document.body.addEventListener('keypress', myKeyPressHandler, false);
//为页面的加载事件附加一个处理函数
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的方式绑定事件处理函数的示例

复制内容到剪贴板
代码:
//找到第一个<form>元素并为它附加“提交”事件处理函数
document.getElementsByTagName_r("form")[0].attachEvent('onsubmit',function(){
    //阻止表单提交
    return stopDefault();
});
//为文档的body元素附加一个按键事件处理函数
document.body.attachEvent('onkeypress', myKeyPressHandler);
//为页面的加载事件附加一个处理函数
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函数的示例代码片段

复制内容到剪贴板
代码:
//等待页面加载完成
addEvent( window, "load", function(){
    //监视用户的任何按键
    addEvent( document.body, "keypress", function(e){
        //如果用户按下了Ctrl+Spance
        if ( e.keyCode == 32 && e.ctrlKey ) {
        
            //显示我们的特别的表单
            this.getElementsByTagName_r("form")[0].style.display = 'block';
            //确保没有怪异的事情发生
            e.preventDefault();
        }
    });
});

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

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

复制内容到剪贴板
代码:
// addEvent/removeEvent written by Dean Edwards, 2005
// with input from Tino Zijdel
// http://dean.edwards.name/weblog/2005/10/add-event/
function addEvent(element, type, handler) {
    //为每一个事件处理函数分派一个唯一的ID
    if (!handler.$$guid) handler.$$guid = addEvent.guid++;
    //为元素的事件类型创建一个哈希表
    if (!element.events) element.events = {};
    //为每一个"元素/事件"对创建一个事件处理程序的哈希表
    var handlers = element.events[type];
    if (!handlers) {
        handlers = element.events[type] = {};
        //存储存在的事件处理函数(如果有)
        if (element["on" + type]) {
            handlers[0] = element["on" + type];
        }
    }
    //将事件处理函数存入哈希表
    handlers[handler.$$guid] = handler;
    //指派一个全局的事件处理函数来做所有的工作
    element["on" + type] = handleEvent;
};
//用来创建唯一的ID的计数器
addEvent.guid = 1;
function removeEvent(element, type, handler) {
    //从哈希表中删除事件处理函数
    if (element.events && element.events[type]) {
        delete element.events[type][handler.$$guid];
    }
};
function handleEvent(event) {
    var returnValue = true;
    //抓获事件对象(IE使用全局事件对象)
    event = event || fixEvent(window.event);
    //取得事件处理函数的哈希表的引用
    var handlers = this.events[event.type];
    //执行每一个处理函数
    for (var i in handlers) {
        this.$$handleEvent = handlers[i];
        if (this.$$handleEvent(event) === false) {
            returnValue = false;
        }
    }
    return returnValue;
};
//为IE的事件对象添加一些“缺失的”函数
function fixEvent(event) {
    //添加标准的W3C方法
    event.preventDefault = fixEvent.preventDefault;
    event.stopPropagation = fixEvent.stopPropagation;
    return event;
};
fixEvent.preventDefault = function() {
    this.returnValue = false;
};
fixEvent.stopPropagation = function() {
    this.cancelBubble = true;
};

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

 

事件的类型

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章