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 相关文章推荐
JQuery困惑—包装集 DOM节点
Oct 16 Javascript
在Javascript里访问SharePoint列表数据的实现方法
May 22 Javascript
深入分析js中的constructor和prototype
Apr 07 Javascript
详解JavaScript数组的操作大全
Oct 19 Javascript
jQuery实现的网格线绘制方法
Jun 20 Javascript
AngularJS 视图详解及示例代码
Aug 17 Javascript
jQuery实用小技巧_输入框文字获取和失去焦点的简单实例
Aug 25 Javascript
jquery 禁止鼠标右键并监听右键事件
Apr 27 jQuery
使用Bootstrap + Vue.js实现表格的动态展示、新增和删除功能
Nov 27 Javascript
浅谈redux以及react-redux简单实现
Aug 28 Javascript
基于javascript的拖拽类封装详解
Apr 19 Javascript
react的hooks的用法详解
Oct 12 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
一个ftp类(ini.php)
2006/10/09 PHP
php基础知识:类与对象(1)
2006/12/13 PHP
php Rename 更改文件、文件夹名称
2011/05/24 PHP
php&amp;mysql 日期操作小记
2012/02/27 PHP
zf框架的校验器使用使用示例(自定义校验器和校验器链)
2014/03/13 PHP
PHP7之Mongodb API使用详解
2015/12/26 PHP
PHP实现根据数组某个键值大小进行排序的方法
2018/03/13 PHP
PHP基于进程控制函数实现多线程
2020/12/09 PHP
js自定义事件代码说明
2011/01/31 Javascript
jQuery在IE下使用未闭合的xml代码创建元素时的Bug介绍
2012/01/10 Javascript
JavaScript中setAttribute用法介绍
2013/07/20 Javascript
JS实现仿Windows7风格的网页右键菜单效果代码
2015/09/11 Javascript
原生js获取iframe中dom元素--父子页面相互获取对方dom元素的方法
2016/08/05 Javascript
jQuery事件对象总结
2016/10/17 Javascript
如何理解Vue的作用域插槽的实现原理
2017/08/19 Javascript
vue+animation实现翻页动画
2020/06/29 Javascript
由浅入深讲解python中的yield与generator
2017/04/05 Python
python3实现随机数
2018/06/25 Python
Python代码块批量添加Tab缩进的方法
2018/06/25 Python
从运行效率与开发效率比较Python和C++
2018/12/14 Python
基于python的opencv图像处理实现对斑马线的检测示例
2020/11/29 Python
如何利用Python matplotlib绘制雷达图
2020/12/21 Python
用纯css3实现的图片放大镜特效效果非常不错
2014/09/02 HTML / CSS
好莱坞百老汇御用王牌美妆:Koh Gen Do 江原道
2018/04/03 全球购物
Otticanet澳大利亚:最顶尖的世界名牌眼镜, 能得到打折季的价格
2018/08/23 全球购物
介绍一下Ruby的多线程处理
2013/02/01 面试题
会计电算化专业个人的自我评价
2013/11/24 职场文书
新驾驶员个人自我评价
2014/01/03 职场文书
大学共青团员个人自我评价
2014/04/16 职场文书
主题实践活动总结
2014/05/08 职场文书
花坛标语大全
2014/06/30 职场文书
食品委托检验协议书范本
2014/09/12 职场文书
党的群众路线教育实践活动心得体会范文
2014/11/05 职场文书
写给领导的感谢信
2015/01/22 职场文书
幼儿园托班开学寄语(2015秋季)
2015/05/27 职场文书
基于Python实现将列表数据生成折线图
2022/03/23 Python