YAOHAIXIAO.COM

HTML(5),CSS(3),JavaScript,DOM,Ajax,JSON,Front-end technologies & Yaohaixiao

热门标签:JavaScript Performance 前端开发 前端性能优化 原创

Rss

Home » Frontend » JavaScript » Events » 浅谈Javascript中的事件流和事件绑定

浅谈Javascript中的事件流和事件绑定

JavaScript 中的事件流

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

冒泡型事件(Dubbed Bubbling)

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

示例(1)冒泡型事件:冒泡型事件流(请用 IE 浏览器打开)

<span id="cnt0">
     <a href="#1" id="link0">点击我触发冒泡型事件流</a>
</span>
<script type="text/javascript">
var cnt = document.getElementById("cnt0");
    link = document.getElementById("link0");
	
cnt.attachEvent('onclick',function(){
	alert('this is an SPAN tag.');
});

link.attachEvent('onclick',function(){
	alert('this is an A tag.');
});
</script>

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

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

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

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

捕获型事件(Event Capturing)

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

示例(2)捕获型事件:捕获型事件流(请用 Firefox 浏览器打开)

<span id="cnt0">
     <a href="#1" id="link0">点击我触发冒泡型事件流</a>
</span>
<script type="text/javascript">
var cnt = document.getElementById("cnt0");
    link = document.getElementById("link0");
	
cnt.addEventListener('click', function(){
	alert('this is an SPAN tag.');
}, true);

link.addEventListener('click', function(){
	alert('this is an A tag.');
}, true);
</script>

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

DOM 事件流

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

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

最后要说的是,根据最近业界在开发的实践过程中的运用,我们一般都采取冒泡型的事件流触发方式,耻结 PPK 的内容:

  • 支持冒泡的事件:keydown、keypress、keyup、click、dbclick、 mousedown、mouseout、mouseover、mouseup、mousemove
  • 不支持冒泡的事件:onload、unload、focus、blur、submit、change(这也是我在前面为什么说“一般使用冒泡事件流”)

用 PPK 的话说,那就是“Mouse and Key Events”支持冒泡,而 Interface Events(也就是《Javascript高级程序设计》里的 HTML(HTML是来构建 interface 的)事件。)则只支持捕获。

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

PPK 虽然做过了测试,但是我还是想实践一下,于是我这里这么处理了下:

<span id="cnt0">
     <a href="#1" id="link0">点击我触发冒泡型事件流</a>
</span>
<script type="text/javascript">
var link = document.getElementById("link0");
	
window.onfocus = function(){
	alert('this is window onfocus.');
};

link.onfocus = function(){
	alert('this is an A tag onfocus.');
};
</script>

有趣的事情发生了!在 IE6 里,当你点击我的第一个示例链接,呵呵,视乎是即冒泡完了又捕获了,在 IE8 中则只冒泡了,不过这个只是你点这个链接的时候发生的情况。接着我又算是试探性地“无意中”测试了下,点击其他的应用程序,又一个让我意想不到的情况发生了,居然先触发了 window.onfocus,接着触发了link.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,不然你就挂了!(在IE浏览不会有问题,如果你访问下面的链接,不要用FF!)

示例(3)window.onfocus:window.onfucus()

What is ‘this’?

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

<span id="cnt0">
     <a href="#1" id="link0">测试 Event 中的 this 指向什么</a>
</span>
<script type="text/javascript">
var link = document.getElementById('link0'), 
addListener = function(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;
        }
    }
};

addListener(link, 'click', function(){
    if (this === window) {
        alert('This is a window object');
    }
	else{
        alert(this.tagName);
	}
});
</script>

示例(3)里,我使用了一个兼容的事件监听函数 addListener。在 Firefox 中 this 指向的 A 标签这个Element 对象,所以我们可以得到 A 标签的 tagName 这个属性‘A’。this 出现问题就在 IE 中,当我们使用 attachEvent 给元素绑定事件监听,那么你点的 A 标签结果 this 指向的却是 window 对象而不是一个 A 标签。晕倒!!!-_-! 所以处理 Event 的时候要小心,问题多多啊,要解决这个 this 关键字的问题,我给你的建议就是你可以考虑直接用传统的 ‘onclick’ 或者修改下前面的绑定事件监听的函数:

function addListener(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(e){
   var evt = e || event;
    return fn.call(context, evt);
  };
  try {
    el.addEventListener(event, wrappedFn, isCapture);
  }
  catch (e) {
    try {
      el.attachEvent('on' + event, wrappedFn);
    }
    catch (e) {
      el['on' + event] = wrappedFn;
    }
  }
}

示例(4)What is right ‘this’:再点点我,看我的‘this’是什么?

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

声明:本文采用BY-NC-SA协议进行授权。转载请注明转自:浅谈Javascript中的事件流和事件绑定

« »

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(Spamcheck Enabled)