jquery源码分析(二)——架构设计
要学习一个库首先的理清它整体架构:
1、jQuery源码大致架构如下:(基于 jQuery 1.11 版本,共计8829行源码)
(21,94) 定义了一些变量和函数jQuery=function(){}
(96,280) 给jQuery添加一些方法和属性,jQuery.fn=jQuery.prototype
(285,347) extend: jQuery的一些继承方法 更容易进行后续的扩展
(349,817) jQuery.extend(): 扩展一些工具方法
(877,2856) Sizzle:复杂选择器的实现
(2880,3042) Callbacks:回调对象——》对函数的统一管理
(3043,3183) Deferred:延迟对象——》对异步的统一管理
(3184,3295) support:功能检测
(3308,3652) data():数据缓存
(3653,3797) queue():队列管理
(3083,4299) attr(),prop() val() addClass():属性操作
(4300,5128) on() trigger():事件的相关方法
(5140,6057) DOM操作:添加 删除 获取 包装 筛选
(6058,6620) css():针对样式的操作
(6621,7854) 提交的数据和Ajax()的操作:ajax() load() getJSON()
(7855,8584) animate():运动的方法
(8585,8792) offset:位置与尺寸的方法
(8826) 对外提供jQuery对象接口:window.jQuery=window.$=jQuery;
贴出一位大牛司徒正美对jquery的剖析:(最近也在读他框架设计一书,深深折服,这里推荐下)
jQuery强在它专注于DOM操作的思路一开始就是对的,以后就是不断在兼容性,性能上进行改进。
- ajax 数据交互
- attributes 属性操作,共分className, 表单元素的value值,属性与特征四大块。
- callbacks 函数列队
- core 种子模块,命名空间,链式结构,domReady,多库共存。
- css 样式操作,引入DE大神的两个伟大的hacks,基本上解决精确获取样式的问题。
- data 数据存取。
- deferred 异步列队(三合一版的函数列队)
- dimensions 元素尺寸的设置读取(来自社区)。
- effects 动画引擎
- event 事件系统(基于DE大神的事件系统与社区的两个插件)
- exports AMD系统(被RequireJS作者说服加几行代码支持其东东)
- manipulation 节点的操作
- offset 元素offsetTop(Left)的设置读取
- queue 列队模块(deferred与data的合体)。
- sizzle 从右到左进行解析的选择器引擎。
- support 特征侦测
- traversing 元素遍历。
2、使用jQuery时,我们经常会在原生DOM对象和jQuery对象之间产生困扰,为什么DOM对象没有这个方法,jQuery实例对象可以轻松实现某些功能。接下来看看jQuery实例对象是怎么创建。
(function( window, undefined ) {
var jQuery = (function() {
// 构建jQuery对象
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
} // jQuery对象原型
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
}
init = jQuery.fn.init = function( selector, context, rootjQuery ) {
// selector有以下7种分支情况:
// DOM元素
// body(优化)
// 字符串:HTML标签、HTML字符串、#id、选择器表达式
// 函数(作为ready回调函数)
// 最后返回伪数组
} init.prototype = jQuery.fn;// 合并内容到第一个参数中,后续大部分功能都通过该函数扩展
// 通过jQuery.fn.extend扩展的函数,大部分都会调用通过jQuery.extend扩展的同名函数
jQuery.extend = jQuery.fn.extend = function() {}; // 在jQuery上扩展静态方法
jQuery.extend({
// ready bindReady
// isPlainObject isEmptyObject
// parseJSON parseXML
// globalEval
// each makeArray inArray merge grep map
// proxy
// access
// uaMatch
// sub
// browser
}); // 到这里,jQuery对象构造完成,后边的代码都是对jQuery或jQuery对象的扩展
return jQuery; })(); window.jQuery = window.$ = jQuery;
})(window);
从这段代码可以看出:
(1)、 jQuery对象不是通过 new jQuery 创建的,而是通过 new jQuery.fn.init 创建的。
(2)、jQuery对象就是jQuery.fn.init对象( new jQuery.fn.init( selector, context, rootjQuery );)
(3)、 jQuery.fn指向了jquery的原型(实际上是用属性名fn代替prototype,为后面编程使用方便,要不然多次使用prototype会很绕):
jquery 原型定义了jquery对象初始化init的一系列方法。
(4)、接下来,定义在jQuery.fn.init,后面又加上这句init.prototype = jQuery.fn :即jQuery.fn.init的原型也指向了jQuery的原型,有点绕哈,合并下:
jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype,实例说明一下,
这说明了我们使用new jQuery.fn.init()创建的实例对象的原型就是jQuery的原型,这样你就可以在jquery实例对象就可以直接调用jQuery这个类原型上挂载的方法了。
(5)、检验一个库的好坏是看这个库的扩展性和兼容性。jQuery.extend = jQuery.fn.extend = function() {}是jQuery对外提供扩展的方法
jQuery之所以如此强大,就是它能很容易实现类方法(虽然javascript没有类的概念,但这里还是用类来理解下,希望不要误解到读者)和实例方法扩展。
下面分析下jQuery实例对象这么创建的。
init = jQuery.fn.init = function( selector, context ) {
var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false)
if ( !selector ) {
return this;
} // Handle HTML strings
if ( typeof selector === "string" ) {
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ]; } else {
match = rquickExpr.exec( selector );
} // Match html or make sure no context is specified for #id
if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array)
if ( match[1] ) {
context = context instanceof jQuery ? context[0] : context; // scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge( this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
) ); // HANDLE: $(html, props)
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
// Properties of context are called as methods if possible
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] ); // ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
} return this; // HANDLE: $(#id)
} else {
elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id !== match[2] ) {
return rootjQuery.find( selector );
} // Otherwise, we inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
} this.context = document;
this.selector = selector;
return this;
} // HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
} // HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
this.context = this[0] = selector;
this.length = 1;
return this; // HANDLE: $(function)
// Shortcut for document ready
} else if ( jQuery.isFunction( selector ) ) {
return typeof rootjQuery.ready !== "undefined" ?
rootjQuery.ready( selector ) :
// Execute immediately if ready is not present
selector( jQuery );
} if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
} return jQuery.makeArray( selector, this );
};
分析上面代码:
1、jQuery.fn.init的功能是对传进来的selector参数进行分析,进行各种不同的处理,然后创建jQuery实例对象。
2、arguments:选择器和使用的上下文环境(比较生涩:举个例子,比如在地图上找深圳这个地方,选择器好比深圳这个地名,熟悉的人可能一下子用经纬度定位了,东经113°46'~114°37',北纬22°27'~22°52'(参数context都不用传了^^),对歪国仁来说,可能第一眼的先定位到我们的公鸡版图:也就是context:china,这样搜索速度就加快了。对国人(闭眼也能找着祖国哈^^)来说直接找广东—靠海—靠香港:也就是context:广东)。
3、接下来看看怎么分析selector参数类型以及相应的做了什么处理。
问题来了,看到这个正则这么长有没有要晕,要吐的感觉,
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,这里写的什么。呃这个暂时不解释,后面专门奉上一个正则表达专题供大家解读…………。
(1) $(""), $(null), $(undefined), $(false);这几种情况直接返回 this,这里this是什么呢? 注意不是JQuery本身,而是jQuery.fn.init
(2)、字符串类型
a:HTML标签形式的——$(<>):通过document.createElement 创建节点。类似 $("<div></div>")、$("<div>aaa</div><div>aaa</div>") 两种情况;
而其他情况如:$(“</div>111”)、$("#id")、$(".class")、$("div")、$("#id .class div"),其中requickExpr匹配的是$(“</div>111”)、$("#id")。如果传入的是$(“</div>111”),则match=["</div>111","</div>",null];如果传入的是$("#id"),则match=["#id",null,“id”]。
b:HTML字符串——$(html) -> $(array):创建DOM并扩充到jQuery对象
c:#id——$(#id):直接通过document.getElementById获取dom节点。
d:选择器表达式——$(expr, $(...)):
(3)、DOMElement:直接返回将该对象,只是修改了context属性和length属性。
(4)、function:用过jquery基本上都会初始化代码写到 $(function(){……})里,就是DOMLoaded 加载完毕之后回调执行改匿名函数。
(5)、slector.slector = undefined 这个眼戳暂时没看懂,有谁看懂提下谢谢 。
上面涉及到的函数这里先提前脑补一下:$.parseHTML 、$.merge、$.isPlainObject(
$.parseHTML :将字符串转换为存储DOM节点的数组 。第一个参数为传入的字符串,第二个为指定的根节点,第三个是boolean值 (“<script></script>”是否可以转化为<script>标签),默认为false,不转换。
$.merge:合并两个数组,在jQuery内部不仅可以合并数组,也可以合并类数组。
$.isPlainObject():判断传入的参数是否是由 {}或new Object 创建的对象。
3、下面看看jQuery原型挂载了什么?
jQuery.fn = jQuery.prototype = {
// The current version of jQuery being used
jquery: version, constructor: jQuery, selector: "", length: 0, toArray: function() {
return slice.call( this );
}, get: function( num ) {
return num != null ?
( num < 0 ? this[ num + this.length ] : this[ num ] ) : slice.call( this );
}, pushStack: function( elems ) { var ret = jQuery.merge( this.constructor(), elems ); ret.prevObject = this;
ret.context = this.context; return ret;
}, each: function( callback, args ) {
return jQuery.each( this, callback, args );
}, map: function( callback ) {
return this.pushStack( jQuery.map(this, function( elem, i ) {
return callback.call( elem, i, elem );
}));
}, slice: function() {
return this.pushStack( slice.apply( this, arguments ) );
}, first: function() {
return this.eq( 0 );
}, last: function() {
return this.eq( -1 );
}, eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
}, end: function() {
return this.prevObject || this.constructor(null);
}, push: push,
sort: deletedIds.sort,
splice: deletedIds.splice
};
1、先来看这三个属性:push、sort、splice 都是数组 Array()原型上挂载的方法。仅供jQuery内部使用(私有方法),与jQuery外部工具方法(对外可使用)不同
2、first、last、eq这几个方法都是都是又来获取数组某个元素的。
3、重点来了:pushStack在jQuery中使用频率特别高,它是干嘛的?只看这段代码。只能知道 它调用了 jQuery.merge,合并了类数组。
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false; if ( typeof target === "boolean" ) {
deep = target; target = arguments[ i ] || {};
i++;
} if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
} if ( i === length ) {
target = this;
i--;
} for ( ; i < length; i++ ) {
if ( (options = arguments[ i ]) != null ) {
for ( name in options ) {
src = target[ name ];
copy = options[ name ]; if ( target === copy ) {
continue;
} if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : []; } else {
clone = src && jQuery.isPlainObject(src) ? src : {};
} target[ name ] = jQuery.extend( deep, clone, copy ); } else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
return target;
};
1、功能:jQuery.extend(object); 为扩展jQuery类本身.为类添加新的方法。jQuery.fn.extend(object);给jQuery实例对象添加方法。
2、结合$.extend使用方法来分析:
从arguments可以看出,extend是为了合并两个或更多对象的属性到第一个对象中。
3、再来分析代码:
看第一个逻辑意思是:如果第一个参数是布尔型的话,要实现扩展的对象target就是第二个参数,接下来再判断此时target是否对象或函数,都不是则默认扩展到{}。
接下来两个for循环,第一个循环遍历后续所有对象,第二个循环 实现将每个对象属性的copy到第一个对象。看似很简单,问题来了,target的属性与需要copy对象属性一样时,怎么处理?是直接覆盖/重写,还是合并?这就涉及到了浅拷贝与深拷贝,那浅拷贝与深拷贝又是什么。
(1)、js对象浅拷贝简单的赋值就是浅拷贝。因为对象和数组在赋值的时候都是引用传递。赋值的时候只是传递一个指针。也就是说遇到同名属性时直接覆盖/重写。
(2)、因为对象相对较为复杂,所以我们先来看对数组的深拷贝的问题
来看个例子,说明一下
extend(boolean,dest,src1,src2,src3...)
第一个参数boolean代表是否进行深度拷贝,其余参数和前面介绍的一致,什么叫深层拷贝,我们看一个例子:
var result=$.extend( true, {},
{ name: "John", location: {city: "Boston",county:"USA"} },
{ last: "Resig", location: {state: "MA",county:"China"} } );
我们可以看出src1中嵌套子对象location:{city:"Boston"},src2中也嵌套子对象location:{state:"MA"},第一个深度拷贝参数为true,那么合并后的结果就是:
result={name:"John",last:"Resig",location:{city:"Boston",state:"MA",county:"China"}} ////浅拷贝,对copy对象同名属性值类型为数组或对象的属性进行合并
也就是说它会将src中的嵌套子对象也进行合并,
而如果第一个参数boolean为false,我们看看合并的结果是什么,如下:
var result=$.extend( false, {},
{ name: "John", location:{city: "Boston",county:"USA"} },
{ last: "Resig", location: {state: "MA",county:"China"} } );
那么合并后的结果就是:
result={name:"John",last:"Resig",location:{state:"MA",county:"China"}}//浅拷贝,对copy对象同名属性值类型为数组或对象的属性直接覆盖/重写。
来看下深拷贝和前拷贝怎么实现的:
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : []; } else {
clone = src && jQuery.isPlainObject(src) ? src : {};
} target[ name ] = jQuery.extend( deep, clone, copy ); } else if ( copy !== undefined ) {
target[ name ] = copy;
}
(1)、浅拷贝:target[ name ] = copy;直接覆盖和容易理解。
(2)、深拷贝稍显复杂,首先判断是数组还是对象,在对嵌套子对象递归调用$.extend方法。
4、jQuery多库共存处理
$符号已经被很多库作为作为命名空间,因此不免会与别的库框架或者插件相冲突。
jQuery引入noConflict函数可以将变量$的控制权让给第一个实现它的那个库,确保jQuery不会与其他库的$对象发生冲突。在运行这个函数后,就只能使用jQuery变量访问jQuery对象。下面来看看代码如何处理的?
var
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,//外部库重写了jQuery,先缓存 // Map over the $ in case of overwrite
_$ = window.$;//外部库重写了$,先缓存起来 jQuery.noConflict = function( deep ) {
if ( window.$ === jQuery ) {//把全局$的控制权交出去,此处,jQuery为局部变量
window.$ = _$;
} if ( deep && window.jQuery === jQuery ) {//把全局jQuery的控制权也交出去,此处局部jQuery与全局jQuery做对比
window.jQuery = _jQuery;
} return jQuery;
};
如果我们需要同时使用jQuery和其他JavaScript库,我们可以使用 $.noConflict()把$的控制权交给其他库。旧引用的$ 被保存在jQuery的初始化; noConflict() 简单的恢复它们。
通过类似swap交换的概念,先把之前的存在的命名空间给缓存起来_$,_jQuery,通过对比当前的命名空间达到交换的目的,首先,我们先判断下当前的的$空间是不是被jQuery接管了,如果是则让出控制权给之前的_$引用的库,如果传入deep为true的话等于是把jQuery的控制权也让出去了
那么具体何时调用才不会引起库命名空间冲突问题呢:这个函数必须在你导入jQuery文件之后,并且在导入另一个导致冲突的库之前使用。
jquery源码分析(二)——架构设计的更多相关文章
- jQuery 源码分析(二十一) DOM操作模块 删除元素 详解
本节说一下DOM操作模块里的删除元素模块,该模块用于删除DOM里的某个节点,也可以理解为将该节点从DOM树中卸载掉,如果该节点有绑定事件,我们可以选择保留或删除这些事件,删除元素的接口有如下三个: e ...
- jQuery 源码分析(二十) DOM操作模块 插入元素 详解
jQuery的DOM操作模块封装了DOM模型的insertBefore().appendChild().removeChild().cloneNode().replaceChild()等原生方法.分为 ...
- jQuery 源码分析(二) 入口模块
jQuery返回的对象本质上是一个JavaScript对象,而入口模块则可以保存对应的节点的引用,然后供其它模块操作 我们创建jQuery对象时可以给jQuery传递各种不同的选择器,如下: fals ...
- Tomcat源码分析二:先看看Tomcat的整体架构
Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- [转]jQuery源码分析系列
文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...
- jQuery源码分析系列(转载来源Aaron.)
声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...
- jQuery源码分析系列——来自Aaron
jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...
- [转] jQuery源码分析-如何做jQuery源码分析
jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...
随机推荐
- oc60--Category 分类 练习
// main.m // Category练习 #import <Foundation/Foundation.h> #import "NSString+NJ.h" // ...
- golomb哥伦布编码——本质上就是通过0来区分商和余数
哥伦布编码是一个针对整数的变长编码方式,详细介绍可以看维基百科.这里简单介绍下: 哥伦布编码使用指定的整数 M 把输入的整数分成两部分:商数 q.余数 r. 商数当做一元编码,而余数放在后面做为可缩短 ...
- wireshark解析rtp协议,流媒体中的AMR/H263/H264包的方法
抓到完整的流媒体包之后,用wireshark打开,其中的包可能不会自动映射成RTP+AMR/H263/H264的包,做如下修改操作即可:1. 把UDP 包解析成RTP/RTCP包.选中UDP包,右键 ...
- 机器人走迷宫(dfs)
http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=1590 #include <stdio.h ...
- ZOJ2477 Magic Cube
题目: This is a very popular game for children. In this game, there's a cube, which consists of 3 * 3 ...
- php 图片生成器
一.需求 最近公司由于有大量的海报要做,而且海报的布局规模都是一样的,只是内容不同,所以老板想我开发一个图片的生成器.可以根据你输入的内容生成海报图片. 具体有需求有以下的需求 1.可以根据将每条数据 ...
- Redis(三)-Ubuntu下安装
Ubuntu 下安装 在 Ubuntu 系统安装 Redi 可以使用以下命令: $sudo apt-get update $sudo apt-get install redis-server 启动 R ...
- BZOJ 4332 FFT+快速幂
思路: 最裸的方程:f[i][j]=Σf[i-1][j-k]*F[k] 诶呦 这不是卷积嘛 f[i]就可以用f[i-1]卷F 求到 但是这样还是很慢 设p[i] 为Σ f[j](1<=j< ...
- Spring boot (12) tomcat jdbc连接池
默认连接池 tomcat jdbc是从tomcat7开始推出的一个连接池,相比老的dbcp连接池要优秀很多,spring boot将tomcat jdbc作为默认的连接池,只要在pom.xml中引入了 ...
- 自学Python十 爬虫实战三(美女福利续)
我又来送福利啦!!!不同于上篇文章,这次我们的爬虫采用了多线程,一直以来被所谓的分布式 多线程 爬虫 给唬的怕怕的.今天就来一发多线程爬虫吧,还能看妹子图,想想就觉得很激动!!! 依然是流程解释: ...