《仔仔细细分析Ext》 Ext的事件模型:Ext.lib.Event类简析(版本:ext2.2)

 

先概览一下 adapter 包,这个文件夹下有四个文件, ext-base.js jquery-bridge.js prototype-bridge.js yui-bridge.js 。从文件名看, Ext 的作者 Jack Slocum 已经通览了目前主流的 js 框架 jquery prototype yui Ext yui 渊源本来就很深)。显然, Ext 已经从这些框架里面借鉴了不少东西。从 代码量来说, Ext 绝对是史上最庞大的 js 库,接进与一个小型的操作系统。唉,不得不感叹一下,分析源码的任务看来也不是能很轻松就搞定的呀,只能慢慢来咯!

关于 Ext 作者本人的资料不是本文重点,有兴趣的可以参阅链接:

http://extjs.com/learn/Tutorial: 走近 ExtJs 之关于作者 Jack 的十个问题

另见作者的个人博客: http://jackslocum.com/blog/



 

 

有关 jquery prototype 的东西先不讨论,且看 ext-base.js

         从整体看, ext-base.js 是一个大的自执行函数 (function(){})() ,也就是说,所有引入了 ext-base.js 的页面,只要一加载,这个函数便执行。这个函数共 2000 多行(连空行),这个函数执行完成后会产生如下的 12 个类(包括一个全局方法 fly() ):

         Ext.lib.Dom

Ext.lib.Event

Ext.lib.Ajax

Ext.lib.Region// 估计是用于布局管理的

Ext.lib.Point// 封装坐标点

Ext.lib.Anim// 动态效果

全局函数 fly()

Ext.lib.AnimBase

Ext.lib.AnimMgr// 这里面有模拟多线程的代码,具体机制还没研究

Ext.lib.Bezier// 居然有贝塞尔曲线,不知道干嘛用的

Ext.lib.Motion// 动作

Ext.lib.Scroll

         很容易可以看出, Ext.lib.Dom Ext.lib.Event 这两个类是对原始 DOM 模型和 Event 机制的重新封装。

        

这里我们先关注一下 Ext.lib.Event ,其它的留待日后慢慢剖析。


 

目前,几乎所有带有图形界面的应用程序,包括操作系统,都是基于事件驱动的,差异在于对事件的处理方式不同。很自然地可以想到,事件驱动方式的核心有三个方面:捕捉、传播、处理。 js 中,事件的捕捉和传播交给程序员可以控制的地方不多,大多由浏览器自己来处理。对于事件的处理,核心的地方有两个: 1 、怎么把自己写的一个函数注册成一个事件的监听器; 2 、怎么从一个对象里面把一个监听器删除掉。

总体来说,为了兼容不同的浏览器,事件监听器的注册和删除要分三种情况来处理: 1 、标准的 DOM2 事件模型使用 addEventListener(arg0,arg1,arg2) removeEventListener(arg0,arg1,arg2) 两个方法来进行,除 IE 外的主流浏览器都支持; 2 IE 的事件模型使用 attachEvent(arg0,arg1) detachEvent(arg0,arg1) 两个方法进行; 3 、原始的 0 DOM 的事件模型,只能把函数句柄直接赋值到对象的 类似于onclick这样的属性。

         Ext.lib.Event 类中,注册监听器和删除监听器正是基于如上所述的情况进行:自定义了一个 doAdd() doRemove() 方法,分别返回三个闭包函数。

                           doAdd: function () {

                if (window.addEventListener) {

                    return function(el, eventName, fn, capture) {

                        el.addEventListener(eventName, fn, (capture));

                    };

                } else if (window.attachEvent) {

                    return function(el, eventName, fn, capture) {

                        el.attachEvent("on" + eventName, fn);

                    };

                } else {

                    return function() {

                    };

                }

            }(),

                           

            doRemove: function() {

                if (window.removeEventListener) {

                    return function (el, eventName, fn, capture) {

                        el.removeEventListener(eventName, fn, (capture));

                    };

                 } else if (window.detachEvent) {

                    return function (el, eventName, fn) {

                        el.detachEvent("on" + eventName, fn);

                    };

                } else {

                    return function() {

                     };

                }

            }()

         Ext 定义的这个 Event 类中的其它方法或多或少都是在调用这两个方法执行底层的操作。与这两个实际办事的马仔函数直接相关的两个是 addListener() removeListener() ,他们直接操纵这两个底层的小弟来做事。当然,他们自己也做点管理上的事情,比如容错处理之类的,它们具体的代码就没有必要一行行去分析了。

         根据《 JavaScript 权威指南》上的说法, js 标准的 Event 接口还包含一些事件的详细的描述,比如事件发生的坐标、时间、按键的值等等。 Ext.lib.Event 其它的一些函数是对这些参数的重新封装,函数都比较短小精悍,理解起来也比较顺畅,不再赘述。

 

这里岔开一点:不管怎么说,脚本最终需要浏览器的 js 引擎去执行,因此更高层次的封装必须通过一些方式转成最基本的 js 引擎能识别的操作进行。从上面的代码可以也可以看出来,无论怎么封装, js 引擎最终能执行的还是 addEventListener removeEventListener 或者 attachEvent detachEvent 这样的标准操作。从这个角度来说的话,只要弄懂 javascript 规范的一些执行原理就可以了,剩下来的都可以说是一些花架子而已。因此可以说, Ext 不过是 Jack Slocum 搞出来的比较炫一点的 js css 混合体而已,当然,不是所有人都能搞出来,呵呵。

         Event 类中另一些有用的方法解析如下:

         1 getScroll: function() {

            var dd = document.documentElement, db = document.body;

            if (dd && (dd.scrollTop || dd.scrollLeft)) {

                return [dd.scrollTop, dd.scrollLeft];

            } else if (db) {

                 return [db.scrollTop, db.scrollLeft];

            } else {

                return [0, 0];

            }

         }

         当文档中产生了滚动条的时候,事件发生的位置信息就需要重新处理一下,通过这个方法可以获得滚动条在文档中的位置。

         与这个方法相关的是 getPageX () getPageY() getXY() 三个方法,分别用来计算有滚动条时事件在文档中的 X Y 坐标和 XY 坐标数组。

         2 getTime() :获取事件产生的时间;

         3 stopEvent() stopPropagation() preventDefault() 三个方法分别用来阻止事件的传播、起泡和阻止对事件默认的处理。(关于事件传播的三个阶段:捕捉阶段、到达目标对象阶段、起泡阶段的详细描述可参阅《 JavaScript 权威指南》);

         4 getEvent() :获取事件对象;

         5 getCharCode() :获取事件发生时的按键信息(鼠标和键盘的按键值);

         6 purgeElement() :清除注册在某个事件类型上的所有监听器;

         7 getListeners(): 获取注册在某个事件类型上的所有监听器 ;

         8 _load() :如果文档已经加载完成,将注册在 window onload 事件上的监听器全部清除掉;

9 _unload() :在文档卸载之前,把注册在 window unload 事件上的所有监听函数执行一遍,然后从缓存数组中清除掉。

10 _tryPreloadAttach() onAvailable() startInterval() 这三个函数的执行机制大致是这样的:在文档还没有加载完成之前,可以通过 onAvailable() 方法给某个对象注册某类事件的监听器, onAvailable() 方法会调用 startInterval() 方法来启动一个轮询来执行 _tryPreloadAttach() ,轮询的周期是 POLL_INTERVAL (默认是 20ms ),轮询次数是 POLL_RETRYS 200 次)(这么来看的话,这种依靠不断轮询来尝试文档是否已经加载完成的方法最长是 20ms*200=4 秒钟)。 _tryPreloadAttach() 方法里面会判断文档是不是已经加载完成,如果加载完成,执行注册的监听器,并把定时器清除掉。

_tryPreloadAttach() 方法还有一个 tryAgain 标志用来说明是不是要进行再次尝试。

(题外话:这种靠不断轮询来尝试判断文档是不是已经加载完成的方式比较有创意,但是会不会引起执行效率的问题呢?值得仔细考虑考虑。)

至此, Ext.lib.Event 类已经简单地分析完成,至于 Ext 的其它类怎么依靠这个封装来做事,以及这个类与 EventObject EventManager 之间的关系,还有大家最关心的 Ext.onReady() 方法到底在做什么。

请待下回分解。

(有意一起解析请加:88403922,注明Ext源码解析)

你可能感兴趣的:(JavaScript,jquery,浏览器,ext,yui)