Javascript 事件流和事件绑定


Posted in Javascript onJuly 16, 2009

事件流

浏览器中的事件流意味着页面上可有不仅一个,甚至多个元素响应同一个事件。而这一个或多个元素响应事件发生的先后顺序在各个浏览器(主要针对IE和Netscape)上是不同的。

冒泡型事件(Dubbed Bubbling)

IE上的解决方案就是冒泡型事件(Dubbed Bubbling)。冒泡型事件的基本思想是,事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。

示例(1):点击我触发冒泡型事件流

示例(1)的XHTML代码结构:

<span id="cnt0">
     <a href=”#1″ id=”link0″>点击我触发冒泡型事件流</a>
</span>

这个示例里我同时给span和a标签绑定了click事件,大家看到了,我们点击这个a标签,一次点击(click)同时触发了两个元素a和span的事件。先触发的是绑定给a标签的click事件,然后触发的是span标签的click事件。也就是前面提到的“页面上可有不仅一个,甚至多个元素响应同一个事件”和“事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发”。

这个示例里我们点击的第一目标是a标签这个链接,它就是前面提到的“最特定的事件目标”,然后才是span这个相对“不特定的事件目标”。再看看我的XHTML代码结构,你会发现a标签包含在span标签中,也就是说span是a标签的父节点,如果大家还不是很清晰的知道他们之间的关系,让我们看看下面的这个DOM树的结构图:

Javascript 事件流和事件绑定

事件触发的顺序是从最特定的目标,沿着DOM树不断的向上触发click事件,就像气泡从下一直上冒的过程一样。“冒泡型”也就是这么得来的,也很形象。这里要说明的是,由于我只给a和span绑定了click事件,所以“冒”到span就到顶了,如果你也给包含他们的p标签还有document绑定click事件,这个冒泡的过程就会一直延续到document的事件触发才结束。

另外要说明的是,在IE5.5中冒泡的最高层DOM节点为document,IE6中则可以支持html节点。在Mozllia1.0及之后的版本也支持冒泡,而它则更可以冒到window窗口对象。

捕获型事件(Event Capturing)

相对IE4.0,Netscape4.0则使用的是捕获型事件的解决方案。这个事件触发的过程则正好和冒泡相反——在捕获型事件中,事件从最不精确的对象(document对象)开始触发,然后到最精确的对象。还是前面的示例,不过现在换由捕获型事件触发(当然你需要在Netscape或Firefox中测试)。

示例(2):点击我触发捕获型事件流

示例(2)的XHTML代码结构:

<span id="cnt1">
     <a href=”#1″ id=”link1″>点击我触发捕获型事件流</a>
</span>

事件触发的循序正好跟前面的冒泡相反,这里我就不赘述了。

DOM 事件流

这个事件流则是W3C制定一个标准规范,它同时支持两种事件流模式,不过是先发生捕获型事件流,再发生冒泡型事件流。

DOM事件流最独特的是,它支持文本节点也触发事件(IE中这不支持)。不过说实话,我现在还看不出来让文本节点支持事件有什么作用。

最后要说的是,根据最近大家在开发的实践过程中的运用,我们一般都采取冒泡型的事件流触发方式,这点我们的IE做的比较成功。至于原因,我想你可以通过上面的解释可以看出,毕竟我们给元素触发事件,肯定是希望从我们最希望先触发(从最精确的)的那个开始。而DOM的先捕获后冒泡我觉得只能用让我很疑惑来形容我的感受。因为如果按照DOM的事件流,我们的事件要被触发2次,而我们一般都只会选择一个类型的事件流,值希望触发一次,很难理解当初W3C是怎么想的?!一个字——晕!可能我的理解能力有限,不过这是我的真是感受。

事件处理函数/监听函数

这里我不想做过多的介绍,我们知道在IE里使用attachEvent(”NAME_OF_EVENT_HANDLER”, fnHandler)给元素绑定事件,而在支持DOM事件流的浏览器里,则使用addEventListener(”NAME_OF_EVENT_HANDLER”, fnHandler, isCapture)。前面我控制FIREFOX中触发捕获事件流,就是通过设置isCapture(ture:捕获;false:冒泡)做到的。还有就是我们传统element.onclick或者element['on'+eventName],这个是所有浏览器都支持的事件绑定的监听器,而且我测试的结果XP下的IE6~8、Firefox2.0~3.5、Safari4.0、Opera9.6.4、Chrome2.0.180都是以冒泡的事件流触发的。更老的浏览器我就没有测试过了,不过根据《Pro Javascript Techniques》里介绍,老的浏览器使用onclick这样的事件绑定,触发的也是冒泡事件流。如果你有兴趣可以淘下那些古董浏览器,测试一番。不过还是有不支持冒泡的事件的,下面我们就讲讲。

那些事件是支持冒泡,那些不支持?

这个是比较有意思的,这里的总结都来自PPK在YAHOO的演讲《Javascript Event》(推荐大家一定看看,很经典!),简单总结PPK的内容,基本上只有onload、unload、focus、blur、submit和change事件是不支持冒泡的,这也是我在前面说“一般使用冒泡事件流”。自然向keydown、keypress、keyup、click、dbclick、mousedown、mouseout、mouseover、mouseup、mousemove。用PPK的话说,那就是“Mouse and Key Events”支持冒泡,而Interface Events(也就是《Javascript高级程序设计》里的HTML(HTML是来构建interface的)事件。)则只支持捕获。

他又说了下是,click是“最安全的”事件,它即支持冒泡,又支持捕获。鼠标事件可以触发click,键盘事件也可以触发click。还有就是在支持DOM事件的浏览器里,focus和blur是只支持捕获的,所以如果你如果用到我下面给出的addEvent函数,在给元素绑定focus或则blur事件时,bCapture一定要设置为true。那么这里就发生了一个问题,IE是不支持捕获的,那不是触发不了这连个事件?呵呵,是个严重的问题哦?不过在IE里使用focus和blur事件的时候,其实触发的是IE的focusin和focusout,当然这两个事件也是只支持冒泡的。

PPK虽然这么说,但是我还是想实践一下,于是我这里这么处理了下,window.onfocus = function(){alert('ok')},lnkOne.onfocus = demoClick;有趣的事情发生了!在IE6里,当你点击我的第一个示例链接,呵呵,视乎是即冒泡完了又捕获了,在IE8中则只冒泡了,不过这个只是你点这个链接的时候发生的情况。接着我又算是试探性地“无意中”测试了下,点击其他的应用程序,又一个让我意想不到的情况发生了,居然先触发了window.onfocus,接着触发了lnk.onfocus!!于是我立刻测试了IE6,一样!视乎IE也“疯狂”了一把,触发了onfocus的捕获哦,哈哈!!!难道IE也支持捕获,IE也疯狂???!!!!还是这个现象有其他的解释??疑惑!?!?!!呵呵,起初我确实这么想,让我惊喜了一把,不过仔细想了想,只是事件执行顺序的原因造成了这样的假象。

呵呵,原来在IE6里,点击链接,先触发了onfocus,弹出提示‘A',然后关闭弹出的提示框窗口时,窗口又获得了焦点,又触发了window的焦点事件。然后是触发了A标签的的click事件,然后关闭弹出的提示窗口时,又让窗口获得了焦点,然后又是A标签获得焦点。而IE8测试正确的,当触发了click事件后,再关闭提示窗口的时候,就不在触发window的focus。哎!空欢喜了一场,我还以为windows的IE支捕获呢!!不过也不是完全没有收获,如果你也像我这么整了,你要注意IE6会折腾两次的,不过只是在你点击的时候才会这样,如果用tab切换获得焦点,就只会触发A标签的focus事件了。

还有在Firefox(我测试的最新的3.5)中,可千万别window.onfocus,不然你就挂了!很抱歉用FIREFOX看我这篇帖子的人,OK了几次后,你就挂了!!呵呵!!!

What is ‘this'?

IE在前面给了我“惊喜”,this也给我惊喜。当然主要是我以前没有注意到,但是YAHOO的工程师们早以发现了。this这个关键字是根据上下文来决定的,在我们的事件绑定的功能函数里,this应该是指向当前的元素节点对象,应该是一个Element对象。我想这个大家应该好理解:

示例代码:


<span id="cnt0">
     <a href=”#1″ id=”link0″>点击我触发捕获型事件流</a>
</span>

<script type=”text/javascript”>
var link = document.getElementById('link1′);
     link.onclick = function(){
          alert(this.tagName);
     };
</script>

这段代码正式我在示例(1)中的处理方式,示例中this指向的a标签这个element对象,所以我们可以得到a标签的tagName这个属性‘A'。示例(2)里,我使用一个兼容的事件监听函数:


function addListener(el, event, fn, bCapture){
  var isCapture = bCapture ? bCapture : false;
  try {
    el.addEventListener(event, fn, isCapture);
  }
  catch (e) {
    try {
      el.attachEvent('on' + event, fn);
    }
    catch (e) {
      el['on' + event] = fn;
    }
  }
}

在我们的支持DOM事件流的浏览器里,我们也可以得到正确的提示this.tagName为‘A'。this出现问题就在IE中,当我们使用attachEvent给元素绑定事件,现在你点击下面的链接:

示例(3):What is ‘this'?

示例代码:
<span id=”cnt2″>
    <a href=”#1″ id=”link2″>What is ‘this'?</a>
</span>

function whatIsThis(){
    if (this === window) {
        alert('This is a window object');
    }
    alert('So, This.tagName is:'+ ‘‘'+ this.tagName +''。');
}

<script type=”text/javascript”>
var cntThree = document.getElementById('cnt2′), lnkThree = document.getElementById('link2′);

addListener(lnkThree, ‘click', whatIsThis);
addListener(cntThree, ‘click', whatIsThis);
</script>

看清楚了吗?如果你在IE6~8中测试,那么你点的是window对象而不是一个a标签。晕倒!!!-_-! 所以你要小心,问题多多啊,要解决这个this关键字的问题,我给你的建议就是你可以考虑直接用传统的'onclick'或者修改下前面的绑定事件监听的函数:

修改后的代码:

function addEvent(el, event, fn, obj, overrideContext, bCapture){
  var context = el, isCapture = bCapture ? bCapture : false, wrappedFn = null;

  if (overrideContext) {
    if (overrideContext === true) {
      context = obj;
    }
    else {
      context = overrideContext;
    }
  }
  wrappedFn = function(){
    return fn.call(context);
  };
  try {
    el.addEventListener(event, wrappedFn, isCapture);
  }
  catch (e) {
    try {
      el.attachEvent('on' + event, wrappedFn);
    }
    catch (e) {
      el['on' + event] = wrappedFn;
    }
  }
}

示例(4):再点点我,看我的‘this'是什么?

好了就这么多了,不知道对你有没有帮助,最后说明下,本文中的部分观点参考至《Javascript高级程序设计》(很好的一本书,推荐大家看看!),addEvent函数借鉴了YUI2.7的_addListener方法,这里也要谢谢YUI那些牛人,向他们致敬!

静态页访问地址:http://img.3water.com/online/jsevent/event.htm(如果你也想体验下我的“惊喜”,请用IE6访问,点击第一个示例链接,但千万不要用Firefox,否则会挂掉,别说我没有提醒你!!!)

Javascript 相关文章推荐
javascript回车完美实现tab切换功能
Mar 13 Javascript
Jquery实现兼容各大浏览器的Enter回车切换输入焦点的方法
Sep 01 Javascript
javascript函数声明和函数表达式区别分析
Dec 02 Javascript
JavaScript中常见的字符串操作函数及用法汇总
May 04 Javascript
利用JS生成博文目录及CSS定制博客
Feb 10 Javascript
Ionic快速安装教程
Jun 03 Javascript
JS中split()用法(将字符串按指定符号分割成数组)
Oct 24 Javascript
AngularJS实践之使用ng-repeat中$index的注意点
Dec 22 Javascript
JS判断时间段的实现代码
Jun 14 Javascript
Node.js利用js-xlsx处理Excel文件的方法详解
Jul 05 Javascript
react-native滑动吸顶效果的实现过程
Jun 03 Javascript
JavaScript实现图片伪异步上传过程解析
Apr 10 Javascript
js 对象是否存在判断
Jul 15 #Javascript
js 实现无缝滚动 兼容IE和FF
Jul 15 #Javascript
兼容IE/Firefox/Opera/Safari的检测页面装载完毕的脚本Ext.onReady的实现
Jul 14 #Javascript
JavaScript 继承详解(四)
Jul 13 #Javascript
JavaScript 继承详解(三)
Jul 13 #Javascript
JavaScript 继承详解(二)
Jul 13 #Javascript
JavaScript 继承详解(一)
Jul 13 #Javascript
You might like
JAVA/JSP学习系列之四
2006/10/09 PHP
php中使用preg_replace函数匹配图片并加上链接的方法
2013/02/06 PHP
JavaScript 判断判断某个对象是Object还是一个Array
2010/01/28 Javascript
js 链式延迟执行DOME
2012/01/04 Javascript
Js 导出table内容到Excel的简单实例
2013/11/19 Javascript
jQuery+HTML5实现图片上传前预览效果
2015/08/20 Javascript
JavaScript实现上下浮动的窗口效果代码
2015/10/12 Javascript
JavaScript中的各种操作符使用总结
2016/05/26 Javascript
Bootstrap布局方式详解
2016/05/27 Javascript
VUEJS实战之构建基础并渲染出列表(1)
2016/06/13 Javascript
jQuery使用each方法与for语句遍历数组示例
2016/06/16 Javascript
JS匹配日期和时间的正则表达式示例
2017/05/12 Javascript
vue2.0中click点击当前li实现动态切换class
2017/06/21 Javascript
import与export在node.js中的使用详解
2017/09/28 Javascript
React Form组件的实现封装杂谈
2018/05/07 Javascript
在layui下对元素进行事件绑定的实例
2019/09/06 Javascript
Array.filter中如何正确使用Async
2020/11/04 Javascript
使用vue编写h5公众号跳转小程序的实现代码
2020/11/27 Vue.js
[33:17]OG vs VGJ.T 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
python访问抓取网页常用命令总结
2017/04/11 Python
Tensorflow之构建自己的图片数据集TFrecords的方法
2018/02/07 Python
解析python实现Lasso回归
2019/09/11 Python
python GUI库图形界面开发之PyQt5选项卡控件QTabWidget详细使用方法与实例
2020/03/01 Python
python实现最速下降法
2020/03/24 Python
Python openpyxl 插入折线图实例
2020/04/17 Python
基于Python把网站域名解析成ip地址
2020/05/25 Python
pytorch 实现L2和L1正则化regularization的操作
2021/03/03 Python
LN-CC中国:高端男装和女装的奢侈时尚目的地
2019/09/14 全球购物
化工操作工岗位职责
2014/04/29 职场文书
文明班集体申报材料
2014/05/23 职场文书
员工薪酬激励方案
2014/06/13 职场文书
党员教师批评与自我批评发言稿
2014/10/15 职场文书
销售会议开幕词
2016/03/04 职场文书
公证书
2019/04/17 职场文书
教你用Python爬取英雄联盟皮肤原画
2021/06/13 Python
Spring Boot 启动、停止、重启、状态脚本
2021/06/26 Java/Android