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

传统的判断dom加载的方法

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

window.onload=function(){ ... } 

 可是onload事件触发过于缓慢,尤为是在存在不少外部图片或者视频文件的时候,为了更好的了解这一点有必要知道一个html文档是如何进行加载的,这里引用一个园友的表述: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文件,从新渲染页面。java

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

 

使用jquery进行开发

 

$(function(){
    ...
})    

//or

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

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

 

源码分析

 

首先$(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->$.bindReadydom

     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(); } } },
相关文章
相关标签/搜索