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();    }} );