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();
}
}
},
jQuery实现DOM加载方法源码分析的更多相关文章
- ElasticSearch 启动时加载 Analyzer 源码分析
ElasticSearch 启动时加载 Analyzer 源码分析 本文介绍 ElasticSearch启动时如何创建.加载Analyzer,主要的参考资料是Lucene中关于Analyzer官方文档 ...
- Springboot学习04-默认错误页面加载机制源码分析
Springboot学习04-默认错误页面加载机制源码分析 前沿 希望通过本文的学习,对错误页面的加载机制有这更神的理解 正文 1-Springboot错误页面展示 2-Springboot默认错误处 ...
- Springboot 加载配置文件源码分析
Springboot 加载配置文件源码分析 本文的分析是基于springboot 2.2.0.RELEASE. 本篇文章的相关源码位置:https://github.com/wbo112/blogde ...
- 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)
目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...
- springboot Properties加载顺序源码分析
关于properties: 在spring框架中properties为Environment对象重要组成部分, springboot有如下几种种方式注入(优先级从高到低): 1.命令行 java -j ...
- Spring Cloud Nacos实现动态配置加载的源码分析
理解了上述Environment的基本原理后,如何从远程服务器上加载配置到Spring的Environment中. NacosPropertySourceLocator 顺着前面的分析思路,我们很自然 ...
- Spring boot加载REACTIVE源码分析
一,加载REACTIVE相关自动配置 spring boot通过判断含org.springframework.web.reactive.DispatcherHandler字节文件就确定程序类型是REA ...
- spring启动component-scan类扫描加载过程---源码分析
http://blog.csdn.net/xieyuooo/article/details/9089441#comments
- Volley 图片加载相关源码解析
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47721631: 本文出自:[张鸿洋的博客] 一 概述 最近在完善图片加载方面的 ...
随机推荐
- Linux基础介绍【第八篇】
Linux网络基础 网线 568A 568B 线序:橙白橙 绿白蓝 蓝白绿 棕白棕 交换机.路由器 交换机:DLINK.H3C.CISCO 交换机(Switch)是一种用于电信号转发的网络设备.它可以 ...
- 【译】Meteor 新手教程:在排行榜上添加新特性
原文:http://danneu.com/posts/6-meteor-tutorial-for-fellow-noobs-adding-features-to-the-leaderboard-dem ...
- 《徐徐道来话Java》(2):泛型和数组,以及Java是如何实现泛型的
数组和泛型容器有什么区别 要区分数组和泛型容器的功能,这里先要理解三个概念:协变性(covariance).逆变性(contravariance)和无关性(invariant). 若类A是类B的子类, ...
- 小丁带你走进git世界一-git简单配置
小丁带你走进git世界一-git简单配置 1.github的简单配置 配置提交代码的信息,例如是谁提交的代码之类的. git config –global user.name BattleHeaer ...
- Entity Framework 6 Recipes 2nd Edition(10-8)译 - >映射插入、修改、删除操作到存储过程
10-8. 映射插入.修改.删除操作到存储过程 问题 想要映射插入.修改.删除操作到存储过程 解决方案 假设已有运动员实体模型,如Figure 10-8所示. 对应的数据库表如Figure 10-9所 ...
- PDO连接mysql数据库
1.PDO简介 PDO(PHP Data Object) 是PHP 5 中加入的东西,是PHP 5新加入的一个重大功能,因为在PHP 5以前的php4/php3都是一堆的数据库扩展来跟各个数据库的连接 ...
- linux tree 命令
使用cmder确实是方便了很多,想看命令帮助信息: $ help tree 以图形显示驱动器或路径的文件夹结构. TREE [drive:][path] [/F] [/A] /F 显示每个文件夹中文件 ...
- Laravel 5.3 登录注册底层实现详解
每个控制器都使用 trait 来引入它们需要的方法 */ 用于处理用户登录认证 用于处理新用户注册 包含重置密码逻辑 用于处理重置密码邮件链接 认证需要的视图 包含了应用的基础布局文件 ...
- mysql交互协议解析——mysql包基础数据、mysql包基本格式
mysql交互协议是开发mysql周边组件常用的协议,如JDBC,libmysql等等. 在此我们要认识到mysql交互协议其实是半双工的交互协议,至于为什么,这里就先挖个小坑,以后再填. 在探讨my ...
- IIS访问共享文件详解
前言 公司同事做了一个报表系统,需要做集群部署,本来是一件挺容易的事,但是部署过程中却遇到啦种种蛋疼问题. 问题1.我们的报表使用的是微软的水晶报表,需要上传报表的配置文件,然后水晶报表提供的控件来读 ...