参考文章: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转化为字符串赋给 rbuggyQSA
1405 rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
1406 rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
//冒号表达式 相当于多个if else
return 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);//3333
console.log(testCacheFunc["12 "]);//3333
//再次使用testCacheFunc时 console.log(cache.hasOwnProperty("12 "));打印为true
var cacheResult = testCacheFunc(13, "4444" );
console.log("cacheResult 13 444: ");
console.log(cacheResult);//4444
console.log(testCacheFunc["13 "]); //4444
var 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.add
promise[ 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.fireWith
deferred[ 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();
}
} );