zoukankan      html  css  js  c++  java
  • jquery源码 DOM加载

    jQuery版本:2.0.3

    DOM加载有关的扩展

    • isReady:DOM是否加载完(内部使用) 
    • readyWait:等待多少文件的计数器(内部使用)
    • holdReady():推迟DOM触发
    • ready():准备DOM触发。
    • jQuery.ready.promise=function(){};  监听DOM的异步操作(内部使用)

    一、$(function(){})和原生window.onload的关系

    这个在面试中也是经常会被问到的。从下面几个角度来分析一下它们的区别

    1、执行时机

    页面加载,先加载节点,再加载文件,比如img文件,flash等。

    $(function(){})DOM加载完执行。可能DOM元素关联的东西并没有加载完。

    window.onload等节点和文件都加载完执行。

    对应的事件监听

    jQuery用的是DOMContentLoaded事件。

    DOMDContentLoaded:原生DOM加载事件,这个事件触发代表DOM加载完了。

    我之前写过一篇文章的页面加载时间分析里也有提到。

    onload对应的是load事件。

    2、个数

    window.onload不能写多个,后面的会覆盖前面的。

    $(function(){})可以写多个。都会执行 。

    3、简化写法

    $(function(){})是$(document) .ready(function(){});的简化写法。

    window.onload没有简化写法。

    二、jQurey如何实现DOM ready的

    jQuery直接调用DOMContentLoaded来实现DOM的ready。但是DOMContentLoaded和onLoad一样,浏览器只执行一次,jQuery用什么判断是否已经执行过呢?document.readyState就是判断这个的依据。

    readyState是document的属性,总共有3个值:

    • loading:文档正在加载中
    • interactive:文档已经加载完成,正在进行css和图片等资源的加载
    • complete:文档的所以资源加载完成

    判断完之后如何回调呢?就是用Promise。jQuery通过new一个$.Deferred(promise)对象来实现对DOM的ready的回调,在DOMContentLoaded中将这个promise给resolve掉,这样就执行了之前注册的回调函数,同时后面新注册的回调也会立刻执行。

    但是在调用promise之前,jQuery执行了一次setTimeout,因为jQuery.Promise是不会产生异步的,这和标准的promise规范是不一样的,所有jQuery自己又手动做了一次setTimeout来实现异步。这样使得无论使用在DOM的ready之前注册的回调还是之后注册的回调都会在异步中执行。

    三、源码整体实现逻辑

    $(fn)==>new一个 jQuery.fn.init(fn)==>返回$(document).ready( fn)。也就是说

    • $(function(){}) =》
    • 调用$(document).ready(function(){})=》
    • 相当于$().ready(fn)实例方法=》
    • 调用的jQuery.ready.promise().done(fn)=》
    • jQuery.ready.promise中不管if还是else最终都是调用jQuery.ready(),并返回promise=》
    • jQuery.ready()里面重点是readyList.resolveWith( document, [ jQuery ] );已完成。至此DOM加载完毕就可以调用fn了。

     

    四、实现细节

    1、重点是:jQuery.ready.promise()方法【巧妙

    如果DOM已经加载完成了,调用jQuery.ready()这个工具方法;

    如果DOM没加载完,监听DOMContentLoaded事件和load事件,等事件发生时回调completed(),最终也是调用jQuery.ready()这个工具方法;

    var    // A central reference to the root jQuery(document)
        rootjQuery,
    
        // The deferred used on DOM ready
        readyList;
    
    jQuery.ready.promise = function( obj ) {
        if ( !readyList ) { //第一次readyList为空可以进来,后续就进不来if了,只执行一次
    
            readyList = jQuery.Deferred(); //第一步,创建延迟对象
            if ( document.readyState === "complete" ) { //DOM加载完成的标志就是document.readyState为complete,如果DOM已经加载好了就直接调工具方法jQuery.ready。
                setTimeout( jQuery.ready );//加定时器是为了兼容IE
            } else {//DOM没有加载完检测,即检测了DOMContentLoaded事件,也检测了load事件;最终走回调completed函数
                // Use the handy event callback
                document.addEventListener( "DOMContentLoaded", completed, false );
                // A fallback to window.onload, that will always work
                window.addEventListener( "load", completed, false ); //因为火狐浏览器会缓存load事件,为了第一时间相应所以对load也监听了
            }
        }
        return readyList.promise( obj );
    };

    completed回调函数如下,最终调用的也是jQuery.ready()。 

        // The ready event handler and self cleanup method
        completed = function() {
        //不管是DOMContentLoaded事件还是load发生,都会取消2个事件监听
        //jQuery.ready()只会触发一次
            document.removeEventListener( "DOMContentLoaded", completed, false );
            window.removeEventListener( "load", completed, false );
            jQuery.ready();
        };

    2、jQuery.ready()工具方法做了些什么 

    先做个测试:

       $(function(arg){
           alert(this); //[object HTMLDocument]
           alert(arg); //jQuery函数
       })

    再做个测试:

    除了$(function(){});$(document).ready(function(){}),也可以把ready当做事件来处理如下。

    <script>
    $(document).on("ready",function(){
        alert(123); //123
    });
    </script>

    之所以会自动弹出123,是因为在jQuery源码中用这句话jQuery( document ).trigger("ready").off("ready");来主动触发了ready事件,触发后再取消掉。

    // Handle when the DOM is ready
    ready: function( wait ) { //参数和holdReady有关
    
        // Abort if there are pending holds or we're already ready
        if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
            return;
        }
    
        // 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;
        }
        //第一步看这里重点,resolveWith改变状态的时候传参了,给done中方法fn传入了参数,
        //document是fn的this指向,jQuery是参数
        // If there are functions bound, to execute
        readyList.resolveWith( document, [ jQuery ] );
        //跟主动触发有关
        // Trigger any bound ready events
        if ( jQuery.fn.trigger ) {
            jQuery( document ).trigger("ready").off("ready");
        }
    },

    跟ready的参数有关的有一个holdReady()。

    先做个测试

    $.holdReady(true);
    $(function () {
        alert(123); //调用了holdReady并传参true,就不能弹出123了
    });

    可以推迟,也可以释放推迟,释放了以后就可以触发了。

    $.holdReady(true);
    $.holdReady(false);
    $(function () {
        alert(123); //释放了holdReady,就弹出123
    });

    这个有什么用?

    比如:

    $.getScript('js/a.js',function(){ //异步加载,不会影响后续代码执行。可能会产生一个问题,alert(2)先执行了a.js还没有加载完
    
    })
    
    $(function () {
        alert(2);//先弹2,后弹出1
    });

    很多时候引入外部文件的时候,都想等外部文件或者插件加载完,再去触发操作,操作可能用到a.js。现在这个顺序不对,会出问题。

    怎样解决这个问题?用holdReady()方法。

        $.holdReady(true); //在这里先hold住
        $.getScript('js/a.js', function () { //异步加载,不会影响后续代码执行。可能会产生一个问题,alert(2)先执行了a.js还没有加载完
            $.holdReady(false); //加载完释放,不hold了就可以弹2了
        })
    
        $(function () {
            alert(2);//先弹2,后弹出1
        });

    再深入一点,holdReady() 要针对的文件可能不止一个,有很多个,所以要等所有的文件都加载完再执行,所以源码中有一个计数的readyWait。

    源码中定义了一个等待栈变量——readyWait,每次执行$.holdReady(true)都会增加压栈,而每次$.holdReady()执行都会弹栈,等空栈的时候就执行jQuery.ready函数,即将promise给resolve掉。

    // 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,
    
    //第二步看这里
    //holdReady推迟DOM的触发
    // Hold (or release) the ready event
    holdReady: function( hold ) {
        if ( hold ) {
            jQuery.readyWait++;//hold为真,让readyWait加加处理
        } else {
            jQuery.ready( true );
        }
    },
    
    // Handle when the DOM is ready
    ready: function( wait ) { //参数和holdReady有关
    
        // Abort if there are pending holds or we're already ready
        //readyWait为0时就不用hold了,执行后面的操作
        if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { 
            return;
        }
    
        // Remember that the DOM is ready
        jQuery.isReady = true; //默认是false
    
        // If a normal DOM Ready event fired, decrement, and wait if need be
        if ( wait !== true && --jQuery.readyWait > 0 ) {
            return;
        }
        //第一步看这里重点,resolveWith改变状态的时候传参了,给done中方法fn传入了参数,
        //document是fn的this指向,jQuery是参数
        // If there are functions bound, to execute
        readyList.resolveWith( document, [ jQuery ] );
        //跟主动触发有关
        // Trigger any bound ready events
        if ( jQuery.fn.trigger ) {
            jQuery( document ).trigger("ready").off("ready");
        }
    },

    刚开始看源码,很多地方理解也不到位,解释可能也不清楚。

    参考:

    http://www.cnblogs.com/aeexiaoqiang/p/6525702.html

    本文作者starof,因知识本身在变化,作者也在不断学习成长,文章内容也不定时更新,为避免误导读者,方便追根溯源,请诸位转载注明出处:http://www.cnblogs.com/starof/p/6856572.html有问题欢迎与我讨论,共同进步。

  • 相关阅读:
    《Java多线程编程核心技术》——多线程与同步
    《垃圾回收的算法与实现》——Python垃圾回收
    命令提示符
    clip
    explorer
    dotnet 命令启动报错
    Superfetch/SysMain
    Windows
    Windows 系统授权服务信息
    Windows 命令
  • 原文地址:https://www.cnblogs.com/starof/p/6856572.html
Copyright © 2011-2022 走看看