• 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有问题欢迎与我讨论,共同进步。

  • 相关阅读:
    Leetcode 15 3Sum
    Leetcode 383 Ransom Note
    用i个点组成高度为不超过j的二叉树的数量。
    配对问题 小于10 1.3.5
    字符矩阵的旋转 镜面对称 1.2.2
    字符串统计 连续的某个字符的数量 1.1.4
    USACO twofive 没理解
    1002 All Roads Lead to Rome
    USACO 5.5.1 求矩形并的周长
    USACO 5.5.2 字符串的最小表示法
  • 原文地址:https://www.cnblogs.com/starof/p/6856572.html
Copyright © 2020-2023  润新知