jQuery实现DOM加载方法源码分析

传统的判断dom加载的方法

使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法:

window.onload=function(){

      ...
} 

但是onload事件触发过于缓慢,尤其是在存在很多外部图片或者视频文件的时候,为了更好的了解这一点有必要知道一个html文档是如何进行加载的,这里引用一个园友的表述:

  1.用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件;

  2.浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件;

  3.浏览器又发出CSS文件的请求,服务器返回这个CSS文件;

  4.浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了;

  5.浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;

  6.服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;

  7.浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它;

  8.Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。杯具啊,突然就少了这么一个元素,浏览器不得不重新渲染这部分代码;

  9.终于等到了</html>的到来,浏览器泪流满面……

  10.等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径;

  11.浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。

可以看到是先加载dom结构后加载对用的资源 比如一个一个img标签  ,浏览器再加载img标签时不会等到src对应的图片加载完成就会执行后面的代码,而onload则必须要等到所有资源加载完成才会触发,所以domContentLoaded 就代替了onload  但是对于ie低版本浏览器来说这种方法还没有实现 ,那么如何实现完美的判断dom加载呢?下面来看jquery的写法:

使用jquery进行开发

$(function(){
    ...
})    

//or

$(doucment).ready(function(){
    ...
})

在稍后的分析中会发现两者并无区别,下面就从这个入口开始一步一步了解jquery的写法:

源码分析

首先$(fn) 是在构造函数里传入了一个函数 在init函数(init?如果不了解jqurey构造函数可以查看博主之前的文章http://www.cnblogs.com/yy-hh/p/4492887.html

// HANDLE: $(function)
// Shortcut for document ready
        } else if ( jQuery.isFunction( selector ) ) {
            return rootjQuery.ready( selector );
        }

如果传入的是一个函数 则会执行 rootjQuery.ready( selector );  rootjQuery是什么呢?

// All jQuery objects should point back to these
rootjQuery = jQuery(document);

其实就是$(document) ,然后执行了一个原型方法ready把函数作为参数传了进去,好的现在视线转移到ready(此方法是原型方法还有工具方法不要混淆)

ready: function( fn ) {
        // Attach the listeners
        jQuery.bindReady();

        // Add the callback
        readyList.add( fn );

        return this;
    },

fn接受了传递进来的函数 先执行了一个工具方法bindReady,视线接着转移

bindReady: function() {
        if ( readyList ) {
            return;
        }

        readyList = jQuery.Callbacks( "once memory" );

        // Catch cases where $(document).ready() is called after the
        // browser event has already occurred.
        if ( document.readyState === "complete" ) {
            // Handle it asynchronously to allow scripts the opportunity to delay ready
            return setTimeout( jQuery.ready, 1 );
        }

        // Mozilla, Opera and webkit nightlies currently support this event
        if ( document.addEventListener ) {
            // Use the handy event callback
            document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );

            // A fallback to window.onload, that will always work
            window.addEventListener( "load", jQuery.ready, false );

        // If IE event model is used
        } else if ( document.attachEvent ) {
            // ensure firing before onload,
            // maybe late but safe also for iframes
            document.attachEvent( "onreadystatechange", DOMContentLoaded );

            // A fallback to window.onload, that will always work
            window.attachEvent( "onload", jQuery.ready );

            // If IE and not a frame
            // continually check to see if the document is ready
            var toplevel = false;

            try {
                toplevel = window.frameElement == null;
            } catch(e) {}

            if ( document.documentElement.doScroll && toplevel ) {
                doScrollCheck();
            }
        }
    },

这个方法看起来复杂,呵呵不要着急一行一行的看 我们现在的分析路线是 $(fn)->$(document).ready->$.bindReady

     if ( readyList ) {
            return;
        }

这里出现了一个新变量readyList  第一次执行的时候由于只有声明没有初始化肯定是undefined所以不会走这里

    // The deferred used on DOM ready
    readyList,
readyList = jQuery.Callbacks( "once memory" );

然后给readyList赋值 其最为成为了一个回调对象 关于jquery回调对象的方法这里不再赘述,回调对象创建了但是目前是没有添加回调方法的

  // Catch cases where $(document).ready() is called after the
 // browser event has already occurred.
        if ( document.readyState === "complete" ) {
            // Handle it asynchronously to allow scripts the opportunity to delay ready
            return setTimeout( jQuery.ready, 1 );
        }
document.readyState表示文档加载的状态,如果加载完成了则可以直接执行ready方法也是也就是执行传递的回调函数,既然已经记载好了就可以直接执行了,使用settimeout是保证函数可以异步加载

接来下来的事情就是用dom2级事件处理程序来监听onload事件 和 domcontentLoaded 既然后者加载速度比前者快为什吗还要多此一举呢?这是因为浏览器可能会缓存事件处理程序onload可能会被缓存而先执行所以都写上谁先触发谁先执行;只不过对于domcontentLoaded是执行的domcontentLoaded方法而不是ready方法,其实domcontentLoaded方法也是最终执行ready方法 :
// Cleanup functions for the document ready method
if ( document.addEventListener ) {
    DOMContentLoaded = function() {
        document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
        jQuery.ready();
    };

} else if ( document.attachEvent ) {
    DOMContentLoaded = function() {
        // Make sure body exi msts, at least, in case IE gets a little overzealous (ticket #5443).
        if ( document.readyState === "complete" ) {
            document.detachEvent( "onreadystatechange", DOMContentLoaded );
            jQuery.ready();
        }
    };
}

只不过是先解除绑定之后再执行确保不会多次触发,对于ie浏览器还有一个特殊的方法就是检测滚动条是可以可以执行 当然前提不在框架页面 ,因为如果dom结构加载好了body才有滚动条

 if ( document.documentElement.doScroll && toplevel ) {
                doScrollCheck();
     }
// The DOM ready check for Internet Explorer
function doScrollCheck() {
    if ( jQuery.isReady ) {
        return;
    }
    try {
        // If IE is used, use the trick by Diego Perini
        // http://javascript.nwbox.com/IEContentLoaded/
        document.documentElement.doScroll("left");
    } catch(e) {
        setTimeout( doScrollCheck, 1 );
        return;
    }

    // and execute any waiting functions
    jQuery.ready();
}

isReady是判断是否已经加载的状态值 只有执行ready工具方法后才会变成true,然后就是不停的检测滚动条 直不报错了执行ready方法;

所以bindReady方法就是一个准备方法,把要执行的函数绑定在回调函数中并且判断何时才能去触发,最终都执行$.ready方法 注意这里的ready是工具方法 不同于上面说的ready原型方法或者叫实例方法

马上就可以看到函数被触发了但是别着急 还没有把传进来的fn添加到回调函数列表中呢,看完bindReady之后我们再回到ready实例方法中

ready: function( fn ) {
        // Attach the listeners
        jQuery.bindReady();

        // Add the callback
        readyList.add( fn );

        return this;
    },

原来是在这里添加的 由于bindReady中调用jQuery.ready时都是采用的异步所以完全添加操作得以在执行之前完成 ,现在可以看最后工具方法ready了吧?当然不是你还要直到另一个方法holdReady

    // Hold (or release) the ready event
    holdReady: function( hold ) {
        if ( hold ) {
            jQuery.readyWait++;
        } else {
            jQuery.ready( true );
        }
    },

代码不多主要就是阻止回调函数触发的,比如我们在代码中间需要加载一个脚本文件并且希望优先于rady事件执行就可以使用此方法先停止执行后再恢复实现动态脚本加载参数为false如果不传就是组织ready事件如果传入就是解除阻止,准备工作终于完成下面开始看jQuery.ready方法:

    // Handle when the DOM is ready
    ready: function( wait ) {
        // Either a released hold or an DOMready/load event and not yet ready
        if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
            // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
            if ( !document.body ) {
                return setTimeout( jQuery.ready, 1 );
            }    

此方法接受一个参数也就是holdReady可能传入的true 这里限制了两个条件才能继续运行 1,wait为true readyWait减一后为0,readyWait是一个计数器,因为holdReady可以执行多次,没执行一次该值加一解除一次该值减一   2,wait不为true 并且isRead为false 因为isReady只用执行到这条if语句后面才能修改为ture所以这是保证不要重复执行的 。正常情况下(没有调用holdReady)都是可以通过的,如果调用了并且wait存在说明有解除但是如果解除次数低于阻止次数还是不行的;

if进来之后又是一个if判断这里是这对ie的一个bug可以忽视 有兴趣查看jQuery官网说明http://bugs.jquery.com/ticket/5443 下面就可以让isReady为true了

// Remember that the DOM is ready
            jQuery.isReady = true;

            // If a normal DOM Ready event fired, decrement, and wait if need be
            if ( wait !== true && --jQuery.readyWait > 0 ) {
                return;
            }

ready状态改变之后并不意味着可以立刻执行回调函数了,在前面判断了没有使用holdReady以及使用了holdReady(false)的情况 这两种情况仅仅可以满足isReady为ture  但是如果使用了holdReady没有传值的情况时只要readyWait减一后大于0还是不能执行但是下次解除时isReady状态已经是true了

// If there are functions bound, to execute
            readyList.fireWith( document, [ jQuery ] );

            // Trigger any bound ready events
            if ( jQuery.fn.trigger ) {
                jQuery( document ).trigger( "ready" ).off( "ready" );
            }

最终创建的回调对象通过fireWith方法执行了,并且把this指向了doument并且把jQuery作为参数传递了进去 最后针对有可能使用 on方法绑定ready事件也进行了trigger触发然后解除绑定;至此完毕 机构比较复杂需要看着源码多理几次,最后贴上主要源码


// Is the DOM ready to be used? Set to true once it occurs.
    isReady: false,

    // A counter to track how many items to wait for before
    // the ready event fires. See #6781
    readyWait: 1,

    // Hold (or release) the ready event
    holdReady: function( hold ) {
        if ( hold ) {
            jQuery.readyWait++;
        } else {
            jQuery.ready( true );
        }
    },

    // Handle when the DOM is ready
    ready: function( wait ) {
        // Either a released hold or an DOMready/load event and not yet ready
        if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
            // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
            if ( !document.body ) {
                return setTimeout( jQuery.ready, 1 );
            }
            // Remember that the DOM is ready
            jQuery.isReady = true;

            // If a normal DOM Ready event fired, decrement, and wait if need be
            if ( wait !== true && --jQuery.readyWait > 0 ) {
                return;
            }

            // If there are functions bound, to execute
            readyList.fireWith( document, [ jQuery ] );

            // Trigger any bound ready events
            if ( jQuery.fn.trigger ) {
                jQuery( document ).trigger( "ready" ).off( "ready" );
            }
        }
    },

    bindReady: function() {
        if ( readyList ) {
            return;
        }

        readyList = jQuery.Callbacks( "once memory" );

        //
        if ( document.readyState === "complete" ) {
            // Handle it asynchronously to allow scripts the opportunity to delay ready
            return setTimeout( jQuery.ready, 1 );
        }

        // Mozilla, Opera and webkit nightlies currently support this event
        if ( document.addEventListener ) {
            // Use the handy event callback
            document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );

            // A fallback to window.onload, that will always work
            window.addEventListener( "load", jQuery.ready, false );

        // If IE event model is used
        } else if ( document.attachEvent ) {
            // ensure firing before onload,
            // maybe late but safe also for iframes
            document.attachEvent( "onreadystatechange", DOMContentLoaded );

            // A fallback to window.onload, that will always work
            window.attachEvent( "onload", jQuery.ready );

            // If IE and not a frame
            // continually check to see if the document is ready
            var toplevel = false;

            try {
                toplevel = window.frameElement == null;
            } catch(e) {}

            if ( document.documentElement.doScroll && toplevel ) {
                doScrollCheck();
            }
        }
    },
时间: 2024-10-22 12:53:33

jQuery实现DOM加载方法源码分析的相关文章

Android编程之Fragment动画加载方法源码详解

上次谈到了Fragment动画加载的异常问题,今天再聊聊它的动画加载loadAnimation的实现源代码: Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { 接下来具体看一下里面的源码部分,我将一部分一部分的讲解,首先是: Animation animObj = fragment.onCreateAnimation(transit, enter, fragm

spring启动component-scan类扫描加载过程---源码分析

有朋友最近问到了 spring 加载类的过程,尤其是基于 annotation 注解的加载过程,有些时候如果由于某些系统部署的问题,加载不到,很是不解!就针对这个问题,我这篇博客说说spring启动过程,用源码来说明,这部分内容也会在书中出现,只是表达方式会稍微有些区别,我将使用spring 3.0的版本来说明(虽然版本有所区别,但是变化并不是特别大),另外,这里会从WEB中使用spring开始,中途会穿插自己通过newClassPathXmlApplicationContext 的区别和联系.

ElasticSearch 启动时加载 Analyzer 源码分析

ElasticSearch 启动时加载 Analyzer 源码分析 本文介绍 ElasticSearch启动时如何创建.加载Analyzer,主要的参考资料是Lucene中关于Analyzer官方文档介绍.ElasticSearch6.3.2源码中相关类:AnalysisModule.AnalysisPlugin.AnalyzerProvider.各种Tokenizer类和它们对应的TokenizerFactory.另外还参考了一个具体的基于ElasticSearch采用HanLP进行中文分词的

SpringMVC加载WebApplicationContext源码分析

from http://blessht.iteye.com/blog/2121845 Spring框架提供了构建Web应用程序的全功能MVC模块,叫Spring MVC,通过Spring Core+Spring MVC即可搭建一套稳定的Java Web项目.本文通过Spring MVC源码分析介绍它的核心实现原理. Tomcat服务器启动入口文件是web.xml,通过在其中配置相关的Listener和Servlet即可加载Spring MVC所需数据.基于Spring MVC最简单的配置如下.

Android 资源加载Resources源码分析(8.0)

我们熟悉的资源加载代码: 1.Activity.getResources(); 2.Context.getResources(); 这2种方式获取的都是Resources对象 先看第一种获取Resources对象源码分析: 说明:(AppcompatActivity中getResource()方法与Activity.getResources()是有区别的.AppcompatActivity是new Resources(...)对象) 一:Activity.getResources()源码分析:

Android App 启动时显示正在加载图片(源码)

微信.QQ.天天动听等程序,在打开时显示了一张图片,然后跳转到相关界面.本文实现这个功能,其实很简单.... 新建两个Activity,LoadingActivity,MainActivity,将LoadingActivity设置为android.intent.action.MAIN.使用TimerTesk,或者Thread将LoadingActivity显示几秒后跳转到MainActivity界面. LoadingActivity: new Timer().schedule(new Timer

Java split方法源码分析

Java split方法源码分析 1 public String[] split(CharSequence input [, int limit]) { 2 int index = 0; // 指针 3 boolean matchLimited = limit > 0; // 是否限制匹配个数 4 ArrayList<String> matchList = new ArrayList<String>(); // 匹配结果队列 5 Matcher m = matcher(inp

invalidate和requestLayout方法源码分析

invalidate方法源码分析 在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewGroup没有重写这些方法),如下: /**  * Mark the area defined by dirty as needing to be drawn. dirty代表需要重新绘制的脏的区域  * If the view is visible, onDraw(Canvas) will be c

jQuery.pushStack()原型方法源码分析

这次分析的方法跟前面不同,虽然pushStack也是原型方法之一,但是我们几乎从不用在页面调用,在参考手册里面也没有这个方法的使用说明,但是这个方法还是非常重要的,在使用很多jQuery的其他方式都会隐式的调用此方法: 它为以下方法提供支持:? ? jQuery 对象遍历:.eq()..first()..last()..slice()..map().? ? DOM 查找.过滤:.find()..not()..filter()..closest()..add()..andSelf().? ? DO