• jQuery源码dom ready分析


      一、前言

      在平时开发web项目时,我们使用jquery框架时,可能经常这样来使用$(document).ready(fn),$(function(){}),这样使用的原因是在浏览器把DOM树渲染好之前,javascript是无法操作没渲染好的DOM节点。

      其实除了$(document).ready(fn),$(function(){})写法外,还有两种让dom渲染完之后执行js的写法:

    $(document).on('ready', fn2)  //通过on事件绑定函数,通过trigger触发也可以达到
    
    jQuery.ready.promise().done(fn); //通过这种方式也可以实现,jQuery.ready.promise()返回一个deferred对象,done(fn)添加回调方法

      其中具体流程图如下(自己简单画了一下,有错请大家指正)

      

     二、源码部分(建议看这部分是,先理解清楚deferred,promise)

      ①$(function(){}) =>到rootjQuery.ready(selector);

      我们知道,jQuery是由new jQuery.fn.init(selector, context, rootjQuery)实例出来的,对接了两个参数,selector,context

     // 构造函数,定义一个局部变量的jQuery
        jQuery = function (selector, context) {
            // jQuery对象实际上是init的构造函数的引用
            return new jQuery.fn.init(selector, context, rootjQuery);
        }

      当我们使用$(function(){}),则选择器selector参数就变成了funciton,jQuery.fn.init函数判断selector为Funtion时,又指向了rootjQuery.ready(selector),就是$(document).ready(fn);

    rootjQuery = $(document)
    jQuery.fn = jquery.prototype = {
      init:function(selector,context,rootjQuery){
    if (jQuery.isFunction(selector)) { // 引用非静态成员ready方法,等价于$(document).ready(selector) return rootjQuery.ready(selector); }
      }
      
    } 

      ②$(document).on("ready",fn) => 到jQuery(document).trigger("ready").off("ready");

      要理解$(document).on("ready",fn),我们要看ready部分

        //扩展方法到jquery****************************************
        jQuery.extend({
            /**
            记录监听DOMContentLoaded事件(DOM是否加载完成),加载完成设置为true(类似一个开关)
            * type {Boolean} 默认为false,表示页面未加载完。当页面DOM加载完成,设置为true
            */
            isReady: false,
     
            /**
            需要预加载的观察者数量
            * type {Number} 观察者数量
            */
            readyWait: 1,
     
            /**
            DOM加载完成,执行预加载
            * @param {Boolean} wait 为true表示锁定委托人,为false表示释放委托人
            */
            ready: function (wait) {
     
                if (wait === true ? --jQuery.readyWait : jQuery.isReady) {
                    return;
                }
     
                // 检测body是否存在(IE的一个bug),存在继续向下执行,不存在进入语句,执行setTimeout定时器,直到body存在
                if (!document.body) {
                    return setTimeout(jQuery.ready);
                }
     
     
                // 开关,记录DOM加载完成
                jQuery.isReady = true;
     
                // 检测所有的观察者是否执行完成
                if (wait !== true && --jQuery.readyWait > 0) {
                    return;
                }
     
    
                // 委托人readyList通知观察者,开始执行回调函数,并将document作为这些函数的上下文环境
                readyList.resolveWith(document, [jQuery]);
     
                // 检测trigger方法是否存在,触发绑定在document上的事件,执行完成之后并解绑
                //    $(document).on('ready', fn2);
                //    $(document).ready(fn1);
                //这里的fn1会先执行,自己的ready事件绑定的fn2回调后执行
                if (jQuery.fn.trigger) {
                    jQuery(document).trigger("ready").off("ready");
                }
            }
        })    

      ③$(document).ready(fn)

        jQuery.fn = jquery.prototype = {
        if (jQuery.isFunction(selector)) {
            // 引用非静态成员ready方法
            return rootjQuery.ready(selector);
        },
         ready: function (fn) {
                    // Add the callback
                    // promise类似一种事件委托,相当于一个创建委托人(已创建则无需创建,通过readyList判断是否已创建),在DOM加载完成之后,委托人会通知观察者done去执行回调函数fn
                    //调委托函数,返回deferred对象,添加done(fn)回调函数
                    jQuery.ready.promise().done(fn);
         
                    return this;
                }
        }

    最后dom ready相关部分源码详细如下:

     // 构造函数,定义一个局部变量的jQuery
        jQuery = function (selector, context) {
            // jQuery对象实际上是init的构造函数的引用
            return new jQuery.fn.init(selector, context, rootjQuery);
        }
        
      // 就绪事件处理程序
        completed = function (event) {
    
            // document.readyState 判断文档加载状态,'complete'代表文档已经完全加载
            
            if (document.addEventListener || event.type === "load" || document.readyState === "complete") {
            
                detach(); //清理方法
                
                jQuery.ready();//执行延迟加载方法
            }
        }
    
        
        // 清理DOMContentLoaded事件处理程序,为DOM事件做好准备,触发jQuery.ready方法
        detach = function () {
            // 标准的W3C监听事件
            if (document.addEventListener) {
                //删除DOMContentLoaded监听事件
                document.removeEventListener("DOMContentLoaded", completed, false);
                window.removeEventListener("load", completed, false);
            } else {
                // 针对IE,非标准的浏览器
                // 删除onreadystatechange监听事件
                document.detachEvent("onreadystatechange", completed);
                window.detachEvent("onload", completed);
            }
        };    
        var  readyList,rootjQuery=$(document);
        jQuery.fn = jquery.prototype = {
        if (jQuery.isFunction(selector)) {
            // 引用非静态成员ready方法
            return rootjQuery.ready(selector);
        },
         ready: function (fn) {
                    // Add the callback
                    // promise类似一种事件委托,相当于一个创建委托人(已创建则无需创建,通过readyList判断是否已创建),在DOM加载完成之后,委托人会通知观察者done去执行回调函数fn
                    //调委托函数,返回deferred对象,添加done(fn)回调函数
                    jQuery.ready.promise().done(fn);
         
                    return this;
                }
        }
        
        
        //扩展方法到jquery****************************************
        jQuery.extend({
            /**
            记录监听DOMContentLoaded事件(DOM是否加载完成),加载完成设置为true(类似一个开关)
            * type {Boolean} 默认为false,表示页面未加载完。当页面DOM加载完成,设置为true
            */
            isReady: false,
     
            /**
            需要预加载的观察者数量
            * type {Number} 观察者数量
            */
            readyWait: 1,
     
            /**
            锁定或释放预加载委托人
            * @param {Boolean} hold 为true表示锁定预加载委托人,为false表示释放委托人
            */
            holdReady: function (hold) {
                if (hold) {
                    jQuery.readyWait++;
                } else {
                    jQuery.ready(true);
                }
            },
     
            /**
            DOM加载完成,执行预加载
            * @param {Boolean} wait 为true表示锁定委托人,为false表示释放委托人
            */
            ready: function (wait) {
     
                // Abort if there are pending holds or we're already ready
                if (wait === true ? --jQuery.readyWait : jQuery.isReady) {
                    return;
                }
     
                // 检测body是否存在(IE的一个bug),存在继续向下执行,不存在进入语句,执行setTimeout定时器,直到body存在
                if (!document.body) {
                    return setTimeout(jQuery.ready);
                }
     
     
                // 开关,记录DOM加载完成
                jQuery.isReady = true;
     
                // 检测所有的观察者是否执行完成
                if (wait !== true && --jQuery.readyWait > 0) {
                    return;
                }
     
    
                // 委托人readyList通知观察者,开始执行回调函数,并将document作为这些函数的上下文环境
                readyList.resolveWith(document, [jQuery]);
     
                // 检测trigger方法是否存在,触发绑定在document上的事件,执行完成之后并解绑
                //    $(document).on('ready', fn2);
                //    $(document).ready(fn1);
                //这里的fn1会先执行,自己的ready事件绑定的fn2回调后执行
                if (jQuery.fn.trigger) {
                    jQuery(document).trigger("ready").off("ready");
                }
            }
        })    
            
            
        // 预加载委托函数
        jQuery.ready.promise = function (obj) {
            // 判断预加载委托人是否存在
            if (!readyList) {
                // 创建一个预加载委托人
                readyList = jQuery.Deferred();
                /**
                W3C标准DOM浏览器
                * 0-uninitialized:XML 对象被产生,但没有任何文件被加载。
                * 1-loading:加载程序进行中,但文件尚未开始解析。
                * 2-loaded:部分的文件已经加载且进行解析,但对象模型尚未生效。
                * 3-interactive:仅对已加载的部分文件有效,在此情况下,对象模型是有效但只读的。
                * 4-complete:文件已完全加载,代表加载成功。
                */
    
                // 检测document内容是否加载完成,加载完成返回true,否者返回false
                if (document.readyState === "complete") {
    
                    /**
                    * 函数延迟0毫秒执行并不是立即执行, 而是等浏览器运行完挂起的事件句柄和已经更新完文档状态之后才
                    * 运行这个函数.详情见《JavaScript Definition Guide 5th Edition(JavaScript权威指南第5版)》
                    * 函数延迟1毫秒或者为空代表立即执行
                    */
                    // 立即调用jQuery.ready方法,执行预加载内容
                    setTimeout(jQuery.ready);
     
                    // 检测是否符合W3C标准事件模型(IE不支持)
                } else if (document.addEventListener) {
                    // 监听DOMContentLoaded事件,当DOM加载完成,触发completed方法
                    // DOMContentLoaded事件在DOM加载完成时触发
                    // completed方法,目的是先删除监听DOMContentLoaded事件,然后执行jQuery.ready()方法。(删除事件,清除内存)
                    document.addEventListener("DOMContentLoaded", completed, false);
     
                    // 若失败,给window.onload注册一个jQuery.ready方法,并在页面加载完成之后执行,目的是兼容低版本浏览器,防止出现预加载失败
                    window.addEventListener("load", completed, false);
     
                    // IE事件模型
                } else {
                    // 监听onreadystatechange事件,当DOM加载完成,触发DOMContentLoaded方法
                    document.attachEvent("onreadystatechange", completed);
     
                    // 同理,若失败,兼容操作.
                    window.attachEvent("onload", completed);
     
                    // 声明一个变量top, 目的:如果在IE下,文档不是嵌套在框架中, 就不断地检测文档是否准备就绪.
                    var top = false;
     
                    try {
                        //检测是否嵌套在框架中,嵌套在框架中返回false,不嵌套返回document.documentElement 
                        //返回嵌入当前window对象的元素(比如 <iframe> 或者 <object>),如果当前window对象已经是顶层窗口,则返回null,如果有框架则document.documentElement返回为null.
                        top = window.frameElement == null && document.documentElement;
                    } catch (e) { }
     
                    // 检测是否存在doScroll方法
                    // IE支持doScroll,doScroll捕捉页面垂直和水平的滚动,他在ondocumentready 事件触发之后,onload事件触发之前触发
                    if (top && top.doScroll) {
                        (function doScrollCheck() {
                            if (!jQuery.isReady) {
     
                                try {
                                    top.doScroll("left");
                                } catch (e) {
                                    return setTimeout(doScrollCheck, 50);
                                }
     
                                detach();
    
                                // 执行jQuery.ready方法,说明页面DOM加载完成,其实就是改变deffered对象的状态,resolvewith完成,触发完成回调done(fn)
                                jQuery.ready();
                            }
                        })();
                    }
                }
            }
            // 返回一个被限制的委托人(只能执行,不能改变的状态的委托人)
            return readyList.promise(obj);
        };
  • 相关阅读:
    线程池中的scheduleAtFixedRate scheduleWithFixedDelay区别
    几道MySQL问题
    【SQL server 2012】复制数据库到另一台机器上
    LeetCode 98. 验证二叉搜索树
    深度学习知识点
    Graph Network Notes
    剑指 Offer 33. 二叉搜索树的后序遍历序列
    剑指 Offer 29. 顺时针打印矩阵
    LeetCode 54. 螺旋矩阵
    LeetCode 50. Pow(x, n)
  • 原文地址:https://www.cnblogs.com/pingfan1990/p/4288490.html
Copyright © 2020-2023  润新知