jQuery3.2.1 源码 解读
来源:互联网 发布:linux 多核运行 编辑:程序博客网 时间:2024/06/05 02:30
参考文章:http://www.cnblogs.com/coco1s/p/5261646.html
http://schifred.iteye.com/blog/2317239
https://segmentfault.com/a/1190000003933990
http://www.cnblogs.com/losesea/p/4415676.html
jquery 整体框架:
一. JS中的模块规范(Common JS,AMD,CMD)
1.1 Common JS
服务器端JS的模块规范,同步的,CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)}.
像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。
1.2 AMD
AMD就只有一个接口:define(id?,dependencies?,factory);
它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中,像这样:
define(['dep1','dep2'],function(dep1,dep2){...});
RequireJS就是实现了AMD规范的呢。
1.3 CMD
CMD是SeaJS 在推广过程中对模块定义的规范化产出
CMD和AMD的区别有以下几点:
1.对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。
2.CMD推崇依赖就近,AMD推崇依赖前置。
//AMD
define([‘./a’,’./b’], function (a, b) {
//依赖一开始就写好 a.test(); b.test();
});
//CMD
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require(‘./a’);
a.test();
... //软依赖 if (status) { var b = requie('./b'); b.test(); }
});
虽然 AMD也支持CMD写法,但依赖前置是官方文档的默认模块定义写法。
3.AMD的api默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的 require,提供 seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。
二. Jquery 预先定义方法入口
(function(window, undefined) { var // 定义了一个对象变量,一个字符串变量,一个数组变量 class2type = {}, core_version = "1.10.2", core_deletedIds = [], // 保存了对象、字符串、数组的一些常用方法 concat push 等等... core_concat = core_deletedIds.concat, core_push = core_deletedIds.push, core_slice = core_deletedIds.slice, core_indexOf = core_deletedIds.indexOf, core_toString = class2type.toString, core_hasOwn = class2type.hasOwnProperty, core_trim = core_version.trim;})(window);
应用时:
jQuery.fn = jQuery.prototype = { // ... // 将 jQuery 对象转换成数组类型 toArray: function() { // 调用数组的 slice 方法,使用预先定义好了的 core_slice ,节省查找内存地址时间,提高效率 // 相当于 return Array.prototype.slice.call(this) return core_slice.call(this); }}
作用:
那么 jQuery 为什么要这样做呢,我觉得:
1、以数组对象的 concat 方法为例,如果不预先定义好 core_concat = core_deletedIds.concat 而是调用实例 arr 的方法 concat 时,首先需要辨别当前实例 arr 的类型是 Array,在内存空间中寻找 Array 的 concat 内存入口,把当前对象 arr 的指针和其他参数压入栈,跳转到 concat 地址开始执行,而当保存了 concat 方法的入口 core_concat 时,完全就可以省去前面两个步骤,从而提升一些性能;
2、另外一点,借助 call 或者 apply 的方式调用,让一些类数组可以直接调用数组的方法。就如上面是示例,jQuery 对象是类数组类型,可以直接调用数组的 slice 方法转换为数组类型。又譬如,将参数 arguments 转换为数组类型:
function test(a,b,c){ // 将参数 arguments 转换为数组 使之可以调用数组成员方法 var arr = Array.prototype.slice.call(arguments); ...}
三.Sizzle
3.1 Sizzle各个模块,整体设计思路
Sizzle各种选择匹配器Expr = Sizzle.selectors
Find:查找函数
Filter:过滤函数
relative: 块间关系处理
Pseudos
(1) 分割解析
对于复杂的选择器表达式,原生的API无法直接对其进行解析,但是却可以对其中的某些单元进行操作,那么很自然就可以采取先局部后整体的策略:把复杂的选择器表达式拆分成一个个块表达式和块间关系。在下图中可以看到,1、选择器表达式是依据块间关系进行分割拆分的;2、块表达式里面有很多伪类表达式,这是Sizzle的一大亮点,而且还可以对伪类进行自定义,表现出很强的工程性;3、拆分后的块表达式有可能是简单选择器、属性选择器、伪类表达式的组合,例如div.a、.a[name = “beijing”]。
(2) 块表达式查找
经过块内查找, 得到了一个基本的元素集合,那如何处理块间关系呢?通过观察可以发现,对一个复杂的选择器表达式存在两种顺序:
• 从左到右:对得到的集合,进行内部逐个遍历,得到新的元素集合,只要还有剩余的代码块,就需要不断地重复查找、过滤的操作。总结下就是:多次查找、过滤。
• 从右到左:对得到的元素集合,肯定包括了最终的元素,而且还有多余的、不符合条件的元素,那么接下来的工作就是不断过滤,把不符合条件的元素剔除掉。
对于“相邻的兄弟关系(+)”、“之后的兄弟关系(~)”,哪种方式都无所谓了,效率没什么区别。但是对于“父子关系”、“祖先后代关系”就不一样了,此时Sizzle选择的是以从右到左为主,下面从两个维度进行解释:
a、设计思路
• 左到右:不断查询,不断缩小上下文,不断地得到新的元素集合
• 右到左:一次查询,多次过滤,第一查找得到的元素集合不断缩小,知道得到最终的集合
b、DOM树
• 左到右:从DOM的上层往底层进行的,需要不断遍历子元素或后代元素,而一个元素节点的子元素或后代元素的个数是未知的或数量较多的
• 右到左:从DOM的底层往上层进行的,需要不断遍历父元素或祖先元素,而一个元素的父元素或者祖先元素的数量是固定的或者有限的
但是从右到左是违背我们的习惯的,这样做到底会不会出现问题呢?答案是会出现错误,请看下面的一个简单DOM树:
在上面的例子中,我们看到当选择器表达式中存在位置伪类的时候,就会出现错误,这种情况下没有办法,准确是第一位,只能选择从左到右。
结论: 从性能触发,采取从右到左; 为了准确性,对位置伪类,只能采取从左到右。
3.2涉及编程技巧
3.2.1 短路表达式
逻辑与操作和逻辑或操作是短路操作,可以应用于任何类型的操作数,而不仅仅是布尔值,在有一个操作数不是布尔值的情况下,这两个操作不一定会返回布尔值。
// rbuggyQSA.length != 0时, 将rbuggyQSA转化为字符串赋给 rbuggyQSA1405 rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );1406 rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );//冒号表达式 相当于多个if elsereturn operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :false;
3.2.2 闭包实现缓存
function createCache() { var keys = []; function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > 10 ) { // Only keep the most recent entries shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。 delete cache[ keys.shift() ]; } console.log("cache:"); console.log(cache.hasOwnProperty("12 ")); //函数即对象 cache函数在实现缓存功能的同时 还作为缓存的存储空间存放键值对 return (cache[ key + " " ] = value); } return cache;}var testCacheFunc = createCache();var cacheResult = testCacheFunc(12, "3333" );console.log("cacheResult 12 333: ");console.log(cacheResult);//3333console.log(testCacheFunc["12 "]);//3333//再次使用testCacheFunc时 console.log(cache.hasOwnProperty("12 "));打印为truevar cacheResult = testCacheFunc(13, "4444" );console.log("cacheResult 13 444: ");console.log(cacheResult);//4444console.log(testCacheFunc["13 "]); //4444var testCacheFunc2 = createCache(); //相当于定义了另一个缓存区域console.log("testCacheFunc2 12 ");console.log(testCacheFunc2["12 "]);//undefined
3.2.3排序数组去重
//有序数组去重函数 function quchong(results){ var elem,i=0,j; var duplicates = []; while ( (elem = results[i++]) ) { console.log("i:" + i ); //从1开始 if ( elem === results[ i ] ) { j = duplicates.push( i ); //返回duplicates长度 } console.log("i:" + i ); } while ( j-- ) { results.splice( duplicates[ j ], 1 ); } } var results = [1,2,3,3,4,4,4,5]; quchong(results); console.log(results);//[1,2,3,4,5]
四 $.callbacks函数管理器实现pub/sub模式
$.Callbacks
用来管理函数队列。采用了观察者模式,通过add添加操作到队列当中,通过fire去执行这些操作。实际上$.Callbacks
是1.7版本从$.Deferred
对象当中分离出来的,主要是实现$.Deferred功能。
应用:
var topics = {};jQuery.Topic = function( id ) { var callbacks, method, topic = id && topics[ id ]; if ( !topic ) { callbacks = jQuery.Callbacks(); topic = { publish: callbacks.fire, subscribe: callbacks.add, unsubscribe: callbacks.remove }; if ( id ) { topics[ id ] = topic; } } return topic;};// Subscribers$.Topic( "mailArrived" ).subscribe( fn1 );$.Topic( "mailArrived" ).subscribe( fn2 );$.Topic( "mailSent" ).subscribe( fn1 );// Publisher$.Topic( "mailArrived" ).publish( "hello world!" );$.Topic( "mailSent" ).publish( "woo! mail!" );
五 $.deferred 对象
jQuery的所有Ajax操作函数,默认返回的就是一个deferred对象。
5.1 为什么需要deferred对象
由于JavaScript单线程的特点,如果某个操作耗时很长,其他操作就必需排队等待。为了避免整个程序失去响应,通常的解决方法是将那些排在后面的操作,写成“回调函数”(callback)的形式。这样做虽然可以解决问题,但是有一些显著缺点:
1.回调函数往往写成函数参数的形式,导致函数的输入和输出非常混乱,整个程序的可阅读性差;
2.回调函数往往只能指定一个,如果有多个操作,就需要改写回调函数。
3.整个程序的运行流程被打乱,除错和调试的难度都相应增加。
Promises就是为了解决这些问题而提出的,它的主要目的就是取代回调函数,成为非同步操作的解决方案。它的核心思想就是让非同步操作返回一个对象,其他操作都针对这个对象来完成。
5.2 $.deferred对象与promise对象
一个$.deferred
对象对象既有promise对象属性,也有promise(obj)方法,deferred.promise()也是deferred对象
promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; }
$.deferred
对象是通过promise.promise( deferred );
扩展来的,promise有相应的回调函数增添机制,但是没有与改变执行状态有关的方法(比如resolve()方法和reject()方法),相关源码如下:
// promise.progress = list.add// promise.done = list.add// promise.fail = list.addpromise[ tuple[ 1 ] ] = list.add;// deferred.notify = function() { deferred.notifyWith(...) }// deferred.resolve = function() { deferred.resolveWith(...) }// deferred.reject = function() { deferred.rejectWith(...) }deferred[ tuple[ 0 ] ] = function() { deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this;};// deferred.notifyWith = list.fireWith// deferred.resolveWith = list.fireWith// deferred.rejectWith = list.fireWithdeferred[ tuple[ 0 ] + "With" ] = list.fireWith;
$.when()
接受多个deferred对象作为参数,当它们全部运行成功后,才调用resolved状态的回调函数,但只要其中有一个失败,就调用rejected状态的回调函数。它相当于将多个非同步操作,合并成一个。
$.deferred整体结构:
jQuery.extend( { Deferred: function( func ) { var tuples = [ // action, add listener, callbacks, // ... .then handlers, argument index, [final state] [ "notify", "progress", jQuery.Callbacks( "memory" ), jQuery.Callbacks( "memory" ), 2 ], [ "resolve", "done", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 0, "resolved" ], [ "reject", "fail", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 1, "rejected" ] ], state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done( arguments ).fail( arguments ); return this; }, "catch": function( fn ) { return promise.then( null, fn ); }, // Keep pipe for back-compat // 若参数为deferred对象: // 注册一个新的deferred对象,起始deferred对象调用notify、resolve、reject方法时触发新deferred对象的同类方法 // 进而执行新deferred对象中progress、done、fail方法注册的函数 // 示例: // var dtd=$.Deferred(),deferred=$.Deferred(); // function wait(dad){ // setTimeout(function(){ // dtd.resovle(); // },3000); // // return dtd.promise(); // } // wait(dtd).pipe($.Deferred())// 获得$.Deferred()返回对象 // .done(fn); // 实现功能似,$.Deferred()返回对象done方法中添加fn函数,dtd的done方法中也添加fn函数 // dtd.resovle方法控制着dtd、$.Deferred()返回对象的done方法执行 // 若参数为普通函数: // 以deferred.resovle方法传入的参数作为函数的参数,直接执行该函数,函数的返回值作为done、fail方法的参数 // 或者将deferred.resovle方法传入的参数作为done、fail方法的参数 // 示例: // var dtd=$.Deferred(); // function wait(dad){ // setTimeout(function(){ // dtd.resovle(a,b,c); // },3000); // // return dtd.promise(); // } // wait(dtd).pipe(fn1) // .done(fn2) // fn1用来格式化dtd.resovle(a,b,c)中a、b、c参数,作为参数传给fn2 // 若fn1不存在,直接传递a、b、c给fn2 // // done,fail,progress状态执行函数顺序排列,tuples数组元素第四项为对应函数在参数arguments中的位置 // Keep pipe for back-compat pipe: function( /* fnDone, fnFail, fnProgress */ ) { return jQuery.Deferred( function( newDefer ) { ………….. // 返回newDefer的promise对象,使其不能使用deferred对象的resolve、reject方法在外部改变状态 // 链式使用done|fail方法注册函数时,启动函数在pipe方法执行中已挂载 }).promise(); }, then: function( onFulfilled, onRejected, onProgress ) { ………………… return jQuery.Deferred( function( newDefer ) { // deferred对象的tuples[i][3]添加resovle函数返回的函数队列 // 通过deferred.notify|resolve|reject方法启动执行,因此是在延迟函数执行过程或完毕以后调用 // progress_handlers.add( ... ) tuples[ 0 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onProgress ) ? onProgress : Identity, newDefer.notifyWith ) ); // fulfilled_handlers.add( ... ) tuples[ 1 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onFulfilled ) ? onFulfilled : Identity ) ); // rejected_handlers.add( ... ) tuples[ 2 ][ 3 ].add( resolve( 0, newDefer, jQuery.isFunction( onRejected ) ? onRejected : Thrower ) ); } ).promise(); // 创建新的deferred对象,链式注册done|fail函数队列 // 该函数队列的触发函数加载到原有deferred对象的tuple[i][3]中 },//then // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; } },//promise deferred = {}; // 以Callbacks构造deferred对象progress、done、fail方法的函数存储域 // promise.progress|done|fail执行该三组队列函数的注册 //deferred.notify |resolve |rejeact| deferred.notifyWith |resolveWith |rejeactWith // 即时执行相应的函数队列,并执行tuple[3]中的函数队列(promise.then方法注册) // done函数队列率先引入改变state状态值,使fail函数队列失效,progress队列锁定的函数,fail函数相应处理 // deferred在延时函数内部使用,延时函数返回promise对象,实现外部接口只能注册add、不能启动fire // Add list-specific methods jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 5 ]; // promise.progress = list.add // promise.done = list.add // promise.fail = list.add promise[ tuple[ 1 ] ] = list.add; // Handle state if ( stateString ) { list.add( function() { // state = "resolved" (i.e., fulfilled) // state = "rejected" state = stateString; }, // rejected_callbacks.disable // fulfilled_callbacks.disable tuples[ 3 - i ][ 2 ].disable, // progress_callbacks.lock tuples[ 0 ][ 2 ].lock ); } // progress_handlers.fire // fulfilled_handlers.fire // rejected_handlers.fire // promise.then方法将函数队列注册在相应的tuple[3]的Callbacks对象里 // tuple[2]中添加tuple[3].fire,以执行tuple[3]中注册的函数队列 list.add( tuple[ 3 ].fire ); // deferred.notify = function() { deferred.notifyWith(...) } // deferred.resolve = function() { deferred.resolveWith(...) } // deferred.reject = function() { deferred.rejectWith(...) } deferred[ tuple[ 0 ] ] = function() { deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this; }; // deferred.notifyWith = list.fireWith // deferred.resolveWith = list.fireWith // deferred.rejectWith = list.fireWith deferred[ tuple[ 0 ] + "With" ] = list.fireWith; } ); // Make the deferred a promise promise.promise( deferred ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; },//Deferred // Deferred helper// var dtd=$.Deferred(); // function wait(dtd){ // setTimeout(function(){ // dtd.resovle() // },3000) // // return dtd.promise() // } // $.when(wait(dtd)).done(function(){console.log("success")}) // // $.when方法主要意图:多个延迟对象同时执行,最末一个执行完毕,调用done方法注册的函数 // 实际境况:触发函数在$.when(dtd)参数dtd函数中 // 函数队列在$.when(dtd).done(fn).fail(fn)中以done|fail方法注册 // 主要问题:dtd.resovle参数传入函数队列 // $.when参数有多个延迟对象[dtd]时,判断dtd执行完毕总时间 // 实现方案:以dtd.resovle方法启动dtd.done()注册的函数队列 // 构造新的deferred对象master,且master.promise()作为返回结果 // master.resovle|reject方法加入dtd.done()函数队列,因此dtd.resovle方法同时启动master的函数队列 // 同时参数由dtd.resovle传入dtd.done(),最终传入master.done|fail方法中 //多个延迟对象通过闭包外参数remaining判断这几个延迟对象是否执行完毕 // 设计:updateFunc方法在$.when()参数延迟对象执行完毕后调用 // 用remaining判断$.when()参数延迟对象是否执行完毕 // remaining==0时触发执行$.when().done()注册的函数队列 // 通过条件语句区分$.when()参数为deferred对象,还是普通函数,或者为空 // 若为延迟对象,通过deferred.done|then注册updateFunc,执行$.when().done()的函数队列 // 若为普通函数,立即调用updateFunc,执行$.when().done()的函数队列,传参为$.when()中参数 // 若为空,立即执行$.when().done()的函数队列 when: function( singleValue ) { var // count of uncompleted subordinates remaining = arguments.length, // count of unprocessed arguments i = remaining, // subordinate fulfillment data resolveContexts = Array( i ), resolveValues = slice.call( arguments ), // the master Deferred master = jQuery.Deferred(), // 通过闭包驻留remaining,用以判断$.when各参数deferred对象均执行完毕 // 执行完毕通过master.resolveWith方法,执行$.when().done()中done方法注册的函数 // subordinate callback factory updateFunc = function( i ) { return function( value ) { resolveContexts[ i ] = this; resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; if ( !( --remaining ) ) { master.resolveWith( resolveContexts, resolveValues ); } }; }; // Single- and empty arguments are adopted like Promise.resolve if ( remaining <= 1 ) { adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, !remaining ); // Use .then() to unwrap secondary thenables (cf. gh-3000) if ( master.state() === "pending" || jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { return master.then(); } } // Multiple arguments are aggregated like Promise.all array elements while ( i-- ) { adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); } return master.promise(); }} );
- jQuery3.2.1 源码 解读
- jquery3.0源码解读(一)Init
- jquery3.0源码解读(二)Extend
- jquery3.0源码解读(三)Selector
- jquery3.0源码解读(四)Callbacks
- JQuery3.1.1源码解读(三)【Sizzle 选择器】
- JQuery3.1.1源码解读(五)【select 函数】
- JQuery3.1.1源码解读(六)【compile】
- JQuery3.1.1源码解读(七)【Callbacks】
- JQuery3.1.1源码解读(八)【Data】
- JQuery3.1.1源码解读(九)【prevObject】
- JQuery3.1.1源码解读(十)【hooks】
- JQuery3.1.1源码解读(十一)【event-main】
- JQuery3.1.1源码解读(十二)【event-extend】
- JQuery3.1.1源码解读(十三)【event-on】
- JQuery3.1.1源码解读(十四)【event-trigger】
- JQuery3.1.1源码解读(十五)【dom-domManip】
- JQuery3.1.1源码解读(十六)【dom-html】
- Python3.5+OpenCV3.2读取图像问题
- POJ3185_The Water Bowls_开关问题-3
- java程序启动的时候,是不是一次性加载所有类
- 常见的中间件有哪些
- [读书笔记]C#学习笔记五: C#3.0自动属性,匿名属性及扩展方法
- jQuery3.2.1 源码 解读
- Dubbo系列(八)Dubbo源码分析之Dubbo中采用的设计模式
- [读书笔记]C#学习笔记四: C#2.0泛型 可控类型 匿名方法和迭代器
- 设计模式之状态模式
- UVA-10652-凸包
- 在 unittest 中使用 logging 模块记录测试数据
- Android中Sqlite数据库的使用
- redis
- session 为什么不见了?谈 IIS 应用程序池的两个重要设置