位置:海鸟网 > IT > JavaScript >

javascript事件系统的详细发展

本文将要介绍一下javascript事件系统的详细发展史。一个完整的事件系统,通常存在以下三个角色:

在w3c没有把其DOM 模型引入网页时,netscape与微软已经逼不及待到快他们熟悉的语言中把相关的DOM模型搞进来了。这其实也怪之父忙于把抄袭其他语言,忽略了自身事件系统的建设。从此世界被划分为两大阵营了。

DOM0时代,这里的DOM指w3c的DOM。双方都设计两种绑定事件的方法,无侵入式与侵入式。你可以说内联式与非内联式的区别。

侵入式,双方都一样。没有办法,那是很早就实现的。那时IE只有抄袭的份,还不敢胡来。

<input name="ruby" onclick="alert(this.nam)" />

然后是无侵入式,这估计是它们都完成了各自的DOM模型,实现对元素节点的索引机制之后的事了。比如有以下网页片断:

<FORM name="form1">

  <input

    type="button"

    name="button1"

    value="aaaa"/>

</FORM>

我们必须自上而下,一步步找到此元素节点才能操作它。注意,那时没有所谓的document.getElementById。网景的做法,把相关绑定的代码放进一个script标签中:

<form name="form1">

  <input

    type="button"

    name="button1"

    value="aaaa"/>

</form>

如果你不想把代码用window.onload = function(){}这代码块括起来,那么你得把这script标签放于表单元素之后。

微软也有一套索引机制,基本与网景的一样,但IE4还引入了document.all与document.all.tags。不过IE还有另一套方式:

<script

   for="button1"

   event="onclick"

   language="JavaScript">

     alert("this.aaa")

 </script>

不过,它用不了this(或者能,我不会),另要求一个script标签对应body中的一个标签,实在很浪费,最终被淘汰出局了。

这就是DOM0的绑定机制,另以内联方式写在标签中的代码,其实相当于以下方式:

<p id="aa" onclick="alert('aaaa')">相当于↓</p>

<script type="text/javascript">

  var p = document.getElementById("aa")

  p.onclick = new Function("alert('aaaa')")//相当于↓

  p.onclick = function(){alert('aaaa')}

</script>

至此,事件系统三个角色都出场了。通过索引机制得到的对象(元素节点什么的),作为事件源,onclick,onmousemove之类的事件属性,它们充当监听器,onclick后面的函数就是回调函数,这是异步执行的。

随着无侵入的兴起,放到web标准中,应该叫做表现行为结构相分离。在标签内写onclick什么的应该唾弃。无侵入式编程有一种让人越写越多代码的欲望。以前总是缩在一个标签内,随时注意双引号与单引号的套嵌,写多了就烦了,不想写了,现在没有这限制,就像脱缰的马,把更多注意力用于兼容更多浏览器与创造新的点子上。好了,写着写着,人们就开始想能不能在同一个元素上绑定两个onclick事件呢?!

<script type="text/javascript">

  var p = document.getElementById("aa")

  p.onclick = function(){alert('第一次')}

  p.onclick = function(){alert('第二次')}

</script>

当然,只能alert第二个,我们当然也可以用一些技巧达到这目的:

<p id="aa" onclick="alert('第一次')">能绑定多个同类型函数</p>

 <script type="text/javascript">

   var p = document.getElementById("aa")

   var addEvent = function(el,type,fn) {

     var type = "on"+type

     var old = el[type];

     if (typeof el[type] != 'function') {

       el[type] = fn

     }else {

       el[type] = function() {

         old();

         fn();

       }

     }

   }

   addEvent(p,"click",function(){alert('第二次')});

   addEvent(p,"click",function(){alert('第三次')});

</script>

但当要用户搞这东西是不行,因此浏览器商把它们做成内置的。顺带还搞了个事件流,也就是允许事件对象在控件间(标签)中传递。IE的一套API是createEventObject, attachEvent, dettachEvent, fireEvent,事件流是自下向上。网景那套就不清楚了,但听说w3c也是从它那一套发展而来,API比较复杂,createEvent, initEvent,addEventListener, removeEventListener dispatchEvent,那个initEvent还有许多版本呢,如initMouseEvent, initKeyEvent,参数非常多,用于更精确的配置。addEventListener拥有三个参数,但第三个参数通常只在事件代理中有用,通常为false,与IE保持一致,自下而上的冒泡。由于w3c的劣性根,总想与IE划分界线,它最高能冒泡到window(IE为document):

event = dom.Event(type);

args =  [event].concat(args);

var parent = caller;

while(!event.isPropagationStopped() && parent){//isPropagationStopped为w3c dom的一个方法,

  dom.events.handle.apply( parent,args);     //判定是否已禁止冒泡

  parent = parent.parentNode || (parent != window) && window;

}

很奇怪的是HTML的parentNode竟然是文档对象。如果是捕获就麻烦多了,这里不谈它。现在看一下多事件绑定时的兼容问题吧。比如上面那个addEvent其实够用了,DE大神的addEvent也是根据DOM0事件搞出来的。但有一些事件是DOM0绝对模拟不了,如FF的DOMMouseScroll事件,因为没有onDOMMouseScroll这个属性,它必须要用addEventListener,但IE,opera,chrome等支持的mousewheel。因此我们还是离不开这些高级的API。一个通用addEvent函数:

var addEvent = (function () {

    if (document.addEventListener) {

        return function (el, type, fn) {

            el.addEventListener(type, fn, false);

        };

    } else {

        return function (el, type, fn) {

            el.attachEvent('on' + type, function () {

                return fn.call(el, window.event);

            });

        }

    }

})();

不过还是有问题,IE下绑定回调函数不是先进先出,详见《说明IE与非IE浏览器在事件绑定的执行顺序问题》。嗯,这些我将留在下一部分讲。

PS,这个系列与《javascript 跨浏览器的事件系统》系列是不一样,这里着重讲述设计一个事件系统遇到的各种各样的问题。而后者则给出具体的解决方案。