还好,这段代码也不多。先不要管其它的东西,我们先来看这段代码本身的流程,然后再来看各个细节是怎么实现的:
如果 docReadyEvent 这个对象为空,就去执行 initDocReady() 这个方法;
接下来再判断,如果 docReadyState 或者 Ext.isReady 这两个标志位为真,立即执行传递进来的 function ,或者按照 options 这个参数里面的 delay 属性的值延迟执行传进来的 function 。
(备注: defer 方法是 Ext 对原生 Function 类的扩展,每个函数都有这个方法,作用是延迟一定的时间再执行。详细描述请翻阅我的 blog :“《仔仔细细分析 Ext 》
Ext 对 Function 类的扩展” )。
否则(文档还没初始化好),把我们传递进来的函数作为一个监听器,添加到 docReadyEvent 这个事件里面去。
结合我们自己做的那个小例子,到这个地方我们可以很敏感地推测出来,在添加监听器这个地方 Ext 肯定把我们传递进来的方法放到某个数组里面去了,然后等文档加载完成的时候再来调用这些方法,就像我们自己做的那样。
只是,这里的数组它不是一个简单的数组,而是作为 docReadyEvent 的一个属性出现的。来看看 docReadyEvent 到底是谁。沿着源码向上查找,我们发现了 docReadyEvent 的身影:
原来它是 Ext.util.Event 的一个实例,好,我们把 Ext.util.Event 揪出来(它是在 util 包下的 Observable.js 这个文件里面定义的):
哈哈,原来它是如此简单的一个对象!我们的推断没错,它里面果然用了一个数组 (listeners) 来放我们的 function !至于它的 addListener() 方法和最终怎么去调用我们传递进来的方法的(看它的 fire() 方法),这里不多罗嗦了,代码也很简单,原理就和我们自己制作的那个小例子相同。
好,到这里,感觉还没有什么太特别的地方。既然 Ext 在文档没有加载完的时候把我们的 function 存起来了,那么,在文档加载完之后它又是在哪里调用的呢?这个调用肯定与上面我们略过的 initDocReady() 这个方法有关,看代码:
- //检测文档是否加载完成
- var initDocReady = function(){
- docReadyEvent = new Ext.util.Event();
- if(Ext.isReady){
- return;
- }
- // no matter what, make sure it fires on load
- //确保文档加载完成事件能触发(这样fireDocReady()方法才能执行,从能设置文档状态并清除轮询定时器)
- E.on(window, 'load', fireDocReady);
- if(Ext.isGecko || Ext.isOpera) {//Gecko和Opera下的文档加载完成检测方法
- document.addEventListener('DOMContentLoaded', fireDocReady, false);
- }
- else if(Ext.isIE){//IE下的文档加载完成检测方法
- docReadyProcId = setInterval(function(){
- try{
- //这一句一直抛出异常,直到文档加载完成
- Ext.isReady || (document.documentElement.doScroll('left'));
- }catch(e){
- return;
- }
- fireDocReady(); //没有异常抛出,则说明文档已经加载完成,手工fire文档加载完成事件
- }, 5);
- document.onreadystatechange = function(){
- if(document.readyState == 'complete'){
- document.onreadystatechange = null;
- fireDocReady();
- }
- };
- }
- else if(Ext.isSafari){//Safari下的文档加载完成检测方法
- docReadyProcId = setInterval(function(){
- var rs = document.readyState;
- if(rs == 'complete') {
- fireDocReady();
- }
- }, 10);
- }
- };
//检测文档是否加载完成 var initDocReady = function(){ docReadyEvent = new Ext.util.Event(); if(Ext.isReady){ return; } // no matter what, make sure it fires on load //确保文档加载完成事件能触发(这样fireDocReady()方法才能执行,从能设置文档状态并清除轮询定时器) E.on(window, 'load', fireDocReady); if(Ext.isGecko || Ext.isOpera) {//Gecko和Opera下的文档加载完成检测方法 document.addEventListener('DOMContentLoaded', fireDocReady, false); } else if(Ext.isIE){//IE下的文档加载完成检测方法 docReadyProcId = setInterval(function(){ try{ //这一句一直抛出异常,直到文档加载完成 Ext.isReady || (document.documentElement.doScroll('left')); }catch(e){ return; } fireDocReady(); //没有异常抛出,则说明文档已经加载完成,手工fire文档加载完成事件 }, 5); document.onreadystatechange = function(){ if(document.readyState == 'complete'){ document.onreadystatechange = null; fireDocReady(); } }; } else if(Ext.isSafari){//Safari下的文档加载完成检测方法 docReadyProcId = setInterval(function(){ var rs = document.readyState; if(rs == 'complete') { fireDocReady(); } }, 10); } };
这段代码看似很长,实际上做的事情并不多。为了兼容浏览器,里面做了三个判断,我们来看最好懂的 IE 的情况,其它的都可以类比。
总体来看,它就两小段代码,第一段实际上是个定时器,每隔 5 毫秒就去尝试让 document 的滚动一下,如果能滚动(也就是 document 已经初始化完成了)就执行 fireDocReady 。从名称可以看出, fireDocReady 方法是在触发“文档准备好了”这个事件。可以推断,在这个方法里面一定会调用我们传进来的那些(被缓存)的方法。
看到第 119 行的 docReadyEvent.fire() 方法了吧,它先做了一些扫尾的工作,设置 Ext.isReady 标志位啦、清除定时器啦等等,然后就去调用了我们的 function 。
好了,解释到这个地方,其它的代码就无需多言了,应该都能轻松地看明白了。这里也提出一个小小的疑问:在 initDocReady() 这个方法里面,是用一个定时器来不断“重复错误”的方式来检测 document 是不是已经加载完成的。我曾经记得某本“经典”的 JAVA 或 C++ 书籍里面告诫程序员,不要企图使用“抛异常”的方式来做常规的、正常的流程判断。不知道有没有人研究过这块“尝试错误”的代码是不是存在什么样的问题(效率或内存)?如果你知道的话,请一定记得告诉我一声(群 88403922 )。