jQuery的隊列模塊主要是為動畫模塊EFFECTS提供支持,單獨抽取出一個命名空間是為了使程序員可以自定義自己的隊列。
具體API的調用方法可以參考這篇博客http://snandy.iteye.com/blog/1978428
二、隊列模塊的代碼結構
低級方法jQuery下有queue,dequeue,_queueHooks這三種方法;低級方法不建議直接在外部調用;
高級方法有.queue,.dequeue,.clearQueue,.delay,.promise
三、實現代碼
更多的注意點,思路請參見代碼中的注釋
jQuery.fn.extend({ //.queue([queuename]);返回第一個匹配元素關聯的函數隊列 //.queue([queueName],newQueue);修改匹配元素關聯的函數隊列,使用函數數組newQueue替換當前隊列 //.queue([queueName],callback(next,hooks));修改匹配元素關聯的函數隊列,添加callback到隊列中 //如果queueName省略,則默認是動畫隊列fx queue:function(type,data){ var setter=2; if(typeof type!=='string'){ //進行參數修正 data=type; type='fx'; setter--; } //靠,這種判斷是獲取還是設置的點子是怎么想出來的 if(arguments.length<setter){ //說明是獲取操作,根據jQuery的思想,獲取的時候,僅獲取首個 return jQuery.queue(this[0],type); } //否則說明是設置操作,根據jQuery的思想,設置的時候,進行遍歷設置 if(data){ this.each(function(i,item){ var queue=jQuery.queue(this,type,data); //確定添加了hooks jQuery._queueHooks(this,type); //如果是動畫隊列,那么首次入隊的時候回自動出隊執行,不必手動調用dequeue if(type==='fx'&&queue[0]!=='inprogress'){ jQuery.dequeue(this,type); } }); } }, dequeue:function(type){ this.each(function(){ jQuery.dequeue(this,type); }); }, //使得隊列中下一個函數延遲執行 delay:function(time,type){ time=jQuery.fx?jQuery.fx.speeds[time]||time:time; type=type||'fx'; //next和hooks的參數賦值是在dequeue的fn.call中,還記得么? return this.queue(type,function(next,hooks){ var timerId=setTimeout(next,time); hooks.stop=function(){ clearTimeout(timerId); } }); }, clearQueue:function(type){ this.queue(type||'fx',[]); }, //針對每一個匹配元素,對其添加監控,當所有匹配元素的type隊列中的函數都執行完畢時,調用Promise的done添加的成功回調函數 promise:function(type,obj){ var elems=this, count=0, i=elems.length, defered=jQuery.Deferred(), hook; if(typeof type!=='string'){ obj=type; type=undefined; } type=type||'fx'; function resolve(){ if(!(--count)){ //如果計數器count變為0 defered.resolveWith(elems,[elems]); } } //添加監控 while(i--){ hook=elems[i]&&data_priv.get(elems[i],type+'queueHooks'); if(hook&&hook.empty){ count++; hook.empty.add(resolve); } } //這里為毛要調用一次呢? // resolve(); return defered.promise(obj); } });
截止目前的myJquey.js代碼全貌
(function(window,undefined){ var rootjQuery, core_version='2.0.3', idExpr=/^#([\w\-]*)$/, //下面兩個正則用于轉駝峰 rmsPrefix = /^-ms-/, rdashAlpha = /-([\da-z])/gi, rnotwhite = /\S+/g,//匹配非空白字符 class2type={}, core_deletedIds=[], core_version='2.0.3', _jQuery=window.jQuery, _$=window.$, core_toString=class2type.toString, core_hasOwn=class2type.hasOwnProperty, core_trim=core_version.trim, core_indexOf=core_deletedIds.indexOf, core_push=core_deletedIds.push, core_concat=core_deletedIds.concat, core_slice=core_deletedIds.slice, //用于jQuery.camelCase轉駝峰函數中 //當replace函數只有一個匹配項時,第二個參數可以是一個函數 //如果repalce中的正則沒有捕獲組,會向這個函數傳遞三個參數:模式的匹配項,模式匹配項在字符串中的位置,原始字符串 //如果replace中的正則有捕獲組,也會向這個函數傳遞三個參數,模式的匹配項,捕獲組的匹配項,模式匹配項在字符串中的位置 fcamelCase=function(all,letter){ return letter.toUpperCase(); }, jQuery=function(selector,context){ return new jQuery.fn.init(selector,context,rootjQuery); }; //jQuery相關實例方法和屬性 jQuery.fn=jQuery.prototype={ jQuery:core_version,//其實就是版本字符串2.0.3 constructor:jQuery,//還原constructor指向 selector:'',//含有連續的整型屬性、length屬性、context屬性,selector屬性(在jQuery.fn.init中設置),preObject屬性(在pushStack中設置) length:0, init:function(selector,context,rootjQuery){ var match,elem; //selector是選擇器表達式 if(!selector){ return this; } if(typeof selector ==='string'){ match=idExpr.exec(selector); context=context||document; if(match){ elem=context.getElementById(match[1]); if(elem&&elem.parentNode){ this[0]=elem; this.length=1; } this.selector=selector; this.context=document; return this; }else{ //說明是復雜的選擇器表達式,這里只考慮javascript原聲方法 //querySelectorAll返回所有匹配元素的nodelist //querySelector返回匹配的第一個元素 return jQuery.merge(this,context.querySelectorAll(selector)); } } //處理selector是DOM元素的情形 if(selector&&selector.nodeType){ this[0]=selector; this.length=1; this.context=selector; return this; } //處理selector是函數的情形 if(jQuery.isFunction(selector)){ return rootjQuery.ready( selector ); } //處理selector是jQuery對象的情形 if(selector.selector){ this.selector=selector.selector; this.context=selector.context; } //處理其他情形 return jQuery.makeArray(selector,this); }, //將jQuery類數組對象轉換為數組 toArray:function(){ return core_slice.call(this); }, //如果傳遞了參數num,代表獲取下標num的DOM元素(num可以為負數) //如果沒有傳遞num,則將jQuery對象轉換為數組后整體返回 get:function(num){ if(num==null){//注意這里不能用!num,因為num可以為0 return this.toArray(); } return num<0?this[num+this.length]:this[num]; }, //入棧 pushStack:function(elems){ var ret=jQuery.merge(this.constructor(),elems); ret.prevObject=this; ret.context=this.context; return ret; }, //遍歷jQuery對象 each:function(callback,args){ //在靜態方法已經指定了callback的執行上下文 return jQuery.each(this,callback,args); }, //加載完成事件方法,這里暫不考慮 ready:function(fn){}, slice:function(){ //注意apply和call的區別 return this.pushStack(core_slice.apply(this,arguments)); }, first:function(){ return this.get(0); }, last:function(){ return this.get(-1); }, eq:function(i){ var length=this.length, j=+i+(i<0?length:0); return this.pushStack(j>=0&&j<length?[this[j]]:[]); }, map:function(callback){ //這種寫法不能指定callback的執行環境,因為在靜態方法jQuery.map并沒有指定callback的執行上下文 // return this.pushStack(jQuery.map(this,callback)); return this.pushStack(jQuery.map(this,function(elem,i){ return callback.call(elem,i,elem); })); }, //與pushStack方法相對應,返回棧的上一級 end:function(){ return this.prevObject||this.constructor(); }, push:core_push, sort:[].sort, splice:[].splice, }; jQuery.fn.init.prototype=jQuery.fn; //可接受的參數類型如下:jQuery.extend([deep],target,object1,[objectN]) jQuery.extend=jQuery.fn.extend=function(){ var target=arguments[0]||{},//指向目標對象 deep=false,//是否進行深度復制 i=1,//表示源對象的起始下標 length=arguments.length,//表示參數個數; options,name,src,copy,copyIsArray;//options指向某個源對象,name指向源對象的某個屬性名,src目標對象某個屬性的原始值,copy某個源對象的某個屬性的值,copyIsArray指示變量copy是否為數組 //首先進行參數修正 if(typeof target==='boolean'){ deep=target; target=arguments[1]||{}; i=2; } //此時target就是jQuery或jQuery.fn if(i===length){ target=this; i--; } //處理target是字符串或者其他情形,這在深度復制中可能出現 // if(typeof target!=='object'||!jQuery.isFunction(target)){ // target={}; // } for(i;i<length;i++){ options=arguments[i]; for(name in options){ src=target[name]; copy=options[name]; if(deep&©&&(jQuery.isPlainObject(object)||(copyIsArray=jQuery.isArray(object)))){ 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{ target[name]=copy; } } } return target; }; //檢查是否是數組或者類數組 function isArrayLike(obj){ var length=obj.length, type=jQuery.type(obj); if(obj&&jQuery.isWindow(obj)){ return false; } if(obj.nodeType===1&&length){ return true; } if(type==='array'){ return true; } if(typeof length==='number'&&(length==0||(length>0&&(length-1) in obj))){ return true; } return false; } jQuery.extend({ //一堆靜態方法和屬性 expando:'jQuery'+(core_version+Math.random()).replace(/\D/g,''), // 該函數用于釋放jQuery對于全局變量$的控制權,可選的參數deep代表是否釋放對全局變量jQuery的控制權 noConflict:function(deep){ if(window.$===jQuery){ window.$=_$; } if(deep&&window.jQuery===jQuery){ window.jQuery=_jQuery; } return jQuery; }, /********isReady,readyWait,holdReay,ready與加載事件有關,暫且略過***********/ isReady:false, readyWait:1, holdReady:function(hold){}, ready:function(){}, /*******/ /****下面是一系列類型檢測的靜態方法*******/ isFunction:function(obj){ //如果使用typeof,在有些瀏覽器中,正則也會返回function,因此這里采用jQuery處理后的方法,jQuery.type return jQuery.type(obj)==='function'; }, isArray:Array.isArray, isWindow:function(obj){ return obj!==null&&obj===obj.window; }, //判斷obj是否為數字或者數字類型的字符串,并且是有效數字 isNumeric:function(obj){ return !isNaN(parseFloat(obj))&&isFinite(obj); }, type:function(obj){ if(obj===null){ return String(null); } //Date,Array等類型typeof都會返回object,function、正則(部分瀏覽器)中 typeof都會返回function if(typeof obj==='object'||typeof obj==='function'){ return class2type[core_toString.call(obj)]||'object'; } return typeof obj; }, //判斷是否為以下兩種情況:1,對象字面量;2,通過new Object()創建 isPlainObject:function(obj){ if(jQuery.type(obj)!=='object'||obj.nodeType||jQuery.isWindow(obj)){ return false; } //如果是純粹的對象,那么obj一定有constructor屬性,并且方法hasOwnPropertyOf一定就在構造函數本身的原型中,而不用通過原型鏈查找得到 if(obj.constructor&&!core_hasOwn.call(obj.constructor.prototype,'isPrototypeOf')){ return false; } return true; }, //檢查是否是空對象 isEmptyObject:function(obj){ for(var name in obj){ return false; } return true; }, /******類型檢測靜態方法結束********/ error:function(msg){ throw new Error(msg); }, //將html字符串轉換為html DOM結構, parseHTML: function( data, context, keepScripts ){ }, parseJSON:JSON.parse, parseXML:function(data){ var xml, tmp; if ( !data || typeof data !== "string" ) { return null; } // Support: IE9 try { tmp = new DOMParser(); xml = tmp.parseFromString( data , "text/xml" ); } catch ( e ) { xml = undefined; } if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }, noop:function(){}, //用于在全局作用域執行javascript代碼,這里暫略 globalEval:function(data){}, //轉換連字符字符串為駝峰類型 camelCase:function(string){ return string.replace(rmsPrefix,'ms-').replace(rdashAlpha,fcamelCase); }, //判斷elem的nodeName是否=name nodeName:function(elem,name){ return elem.nodeName&&elem.nodeName.toLowerCase()==name.toLowerCase(); }, //jQuery遍歷方法,其中args是傳遞給回調callback的參數,僅供jQuery內部使用;外部調用該方法時,回調的參數默認為數組下標/對象key,對應數組值/對象value each:function(object,callback,args){ var i, value, length=object.length, isArray=isArrayLike(object); if(args){//說明是內部調用 if(isArray){ for(i=0;i<length;i++){ value= callback.call(object[i],args); if(value===false){ break; } } }else{ for(i in object){ value=callback.call(object[i],args); if(value===false){ break; } } } }else{ if(isArray){ for(i=0;i<length;i++){ value=callback.call(object[i],i,object[i]); if(value===false){ break; } } }else{ for(i in object){ value=callback.call(object[i],i,object[i]); if(value===false){ break; } } } } return object; }, trim:function(str){ return str==null?'':core_trim.call(str); }, //將一個類數組對象轉換為真正的對象 //results參數僅供jquery內部使用,此時在該參數的基礎上添加元素 makeArray:function(array,results){ var ret=results||[], type=jQuery.type(array); //undefined,null都會==null if(array!=null){ //1,沒有length屬性,或者具有length屬性,但是是以下幾種情況的 //2.如果array是string 的length表示字符串的長度 //3.如果array是函數,其length代表函數生命時的參數個數 //4,如果array是window對象,屬性Length返回窗口中的框架(frame,iframe)個數 if(array.length==null|| type=='string' || type=='function' ||type=='regexp'||jQuery.isWindow(array)){ core_push.call(ret,array); }else{//否則說明是類數組對象 jQuery.merge(ret,array); } } return ret; }, inArray:function(elem,array,i){ return array==null?-1:core_indexOf.call(array,elem,i); }, //用于合并兩個數組的元素到第一個數組中 //事實上,jquery源代碼中第一個參數可以是數組或者類數組對象,第二個參數可以是數組、類數組對象或任何含有連續整型屬性的對象 //第一個參數是數組,最后返回數組;第一個參數是類數組,則返回類數組 merge:function(first,second){ var l=second.length, i=first.length, j; if(typeof l=='number'){ for(j=0;j<l;j++){ first[i++]=second[j]; } }else{ while(second[j]!=undefined){ first[i++]=second[j++]; } } first.length=i; return first; }, //用于查找數組中滿足過濾函數的元素,形成新的數組之后返回,原數組不受影響 //如果inv未傳入或者是false,元素只有在過濾函數返回true時,才會被保存在最終的結果數組中 //如果參數inv是true,則恰好相反 grep:function(elems,callback,inv){ var i, ret=[], length=elems.length, retVal; inv=!!inv; for(i=0;i<length;i++){ retVal=!!callback.call(elems[i],i); if(retVal!==inv){ ret.push(elems[i]); } } return ret; }, //用于對數組中每個元素執行callback操作,并將結果形成新的數組返回 //參數arg僅僅是jQuery內部使用 map:function(elems,callback,arg){ var ret=[], retVal, i, length=elems.length, isArray=isArrayLike(elems); if(isArray){ for(i=0;i<length;i++){ retVal=callback(elems[i],i,arg);//注意不是callback.call if(retVal!=null){ ret.push(retVal); } } }else{ for(i in elems){ retVal=callback(elems[i],i,arg); if(retVal!=null){ ret.push(retVal); } } } //保證最終返回的是一維數組 return core_concat.call([],ret); }, guid:1, //該方法用于更改函數的執行上下文 //源代碼中有兩種傳參形式,這里僅考慮最常見的一種 proxy:function(fn,context){ if(!jQuery.isFunction(fn)){ return undefined; } var args=core_slice.call(arguments,2); proxy=function(){ return fn.call(context||this,core_concat.call(args,core_slice.call(arguments))); }; proxy.guid=fn.guid=fn.guid||jQuery.guid++; return proxy; }, //用一個方法同時實現get和set操作 //如何設置或者獲取由回調函數fn確定 //這個方法的實現等用到的時候結合來看 access: function( elems, fn, key, value, chainable, emptyGet, raw ){ }, now:Date.now, //該方法用于交換css樣式,在support模塊較多用到 //要交換的樣式由參數options傳遞 swap: function( elem, options, callback, args ){ var name,ret, old={}; for(name in options){ old[name]=elem.style[name]; elem.style[name]=options[name]; } ret=callback.call(elem,args||[]); for(name in options){ elem.style[name]=old[name]; } return ret; }, }); //目前,js中typeof的返回值有六種:"number," "string," "boolean," "object," "function," 和 "undefined." //通過object.prototype.toString/或者{}.toString 返回值有九種:Boolean Number String Function Array Date RegExp Object Error,其中的Array,Date,RegExp,Object,Error都屬于Object類型,在有些瀏覽器中typeof 正則會返回function jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(i,name){ class2type["[object "+name+"]"]=name.toLowerCase(); }); //console.log(class2type,class2type); rootjQuery=jQuery(document); /****接下來這一部分,在jQuery的源代碼中,本來是Sizzle,這里暫且略過***/ var optionsCache={}; function createOptions(options){ var object=optionsCache[options]={}; //\S+,匹配非空格字符 //正則表達式如果沒有g,僅匹配第一個匹配項 jQuery.each(options.match(/\S+/g),function(i,item){ object[item]=true; }); return object; } //參數options可以是字符串或者是對象形式,可選屬性/字符串組合有 //once:回調函數列表只能執行一次 //memory:fire調用之后,再次add將立即觸發 //unique:同一個函數不能被重復添加到回調函數列表中 //stopOnFlase:當某一個函數返回false時,為true的時候,回調函數列表的執行終止 jQuery.Callbacks=function(options){ options=typeof options==='string'?optionsCache[options]||createOptions(options):options||[]; var list=[],//用于存儲回調函數列表 firingStart, once=options.once, memory,//初始值為undefined,只有在memory模式下,調用fire后才會被賦值,以備在add中再次調用 fired=false,//指示是否fire過 firingIndex,//指向要執行的下一個回調函數的下標 add=function(arg){ var type; jQuery.each(arg,function(i,item){ type=jQuery.type(item); if(type==='function'&&!(options.unique&&self.has(item))){ list.push(item); }else if(type ==='array'){ add(item); } }); }, fire=function(data){ fired=true; memory=options.memory&&data; firingIndex=firingStart||0; firingStart=0;//在memory模式下,add的時候firingStart可能會被置為其他值,這里將其還原,以備下次調用fire的時候從頭開始執行 var length; if(!list){ return; } for(length=list.length;firingIndex<length;firingIndex++){ if(list[firingIndex].apply(data[0],data[1])===false&&options.stopOnFalse){ break; } } // if(once){ // if(memory){//如果通知是once和memory模式,那么在add的時候可以進行再次觸發 // list=[]; // }else{//否則直接禁用 // self.disable(); // } // } }, self={ add:function(){ if(list){ var start=list.length; add(arguments); //如果是memory模式下的add,會導致立即觸發 if(memory){//memory的初始值為undefined,memory模式下調用一次fire才會被賦值,因此第一次調用add的時候不會走下面 firingStart=start; fire(memory); } } return this; }, remove:function(){ if(list){ var i; jQuery.each(arguments,function(i,item){ //jQuery.inArray(item,list,i),返回item在list中的下表,從第i位向后數,包括第i為 while((i=jQuery.inArray(item,list,i))>-1){ list.splice(i,1);//刪除上的數值 } }); } return this; }, //fn有值的時候,代表判斷回調函數列表是否存在函數fn //沒有參數fn的時候,代表判斷回調函數列表是否為空 has:function(fn){ return fn?jQuery.inArray(fn,list)>-1:!!(list&&list.length); }, empty:function(){ if(list){ list=[]; } return this; }, disable:function(){ //list就不用說了,list置為undefined之后,幾乎所有的方法都不能調用 //memory恢復初始值undefined list=memory=undefined; return this; }, disabled:function(){ return !list; }, fireWith:function(context,args){ if(list&&!(once&&fired)){ args=args||[];//主要是為了處理args為undefined的情況 args=[context,args.slice?args.slice():args]; fire(args); } return this; }, fire:function(){ self.fireWith(this,arguments); return this; }, fired:function(){ return !!fired; }, //自己加的函數,供調試用 getList:function(){ return list; } }; return self; }; //實現異步隊列Defered,When //異步隊列內部維護了三個回調函數列表,分別是成功,失敗,消息 jQuery.extend({ //func參數僅內部使用,func的調用者是jQuery.Deferred的返回值,參數也是 Deferred:function(func){ var doneList=jQuery.Callbacks('once memory'), failList=jQuery.Callbacks('once memory'), progressList=jQuery.Callbacks('memory'), state='pending', list={ 'resolve':doneList, 'reject':failList, 'notify':progressList }, promise={ done:doneList.add, fail:failList.add, progress:progressList.add, state:function(){ return state; }, //同時添加成功,失敗,消息回調函數 then:function(doneCallback,failCallback,progressCallback){ deferred.done(doneCallback).fail(failCallback).progress(progressCallback); }, //成功,失敗時,添加同一個處理函數 always:function(){ deferred.done(arguments).fail(arguments); }, //說實話,能看懂這個源代碼,但搞不太懂這個pipe是干嘛用的 //實際使用中調用的地方也不多 //不過其源代碼有不少知識點值得學習 pipe:function(fnDone,fnFail,fnProgress){ //這里的newDefer,就是調用jQuery.Deferred(function(newDeferred))返回的異步隊列對象,由這部分代碼最終的func.apply(deferred,deferred)決定; return jQuery.Deferred(function(newDefer){ jQuery.each({ done:[fnDone,'resolve'], fail:[fnFail,'reject'], progress:[fnProgress,'notify'] },function(handler,data){ //注意這三個局部變量定義的位置,只能定義在該閉包中,如果定義在jQuery.Deferred得到的只是函數最后的值,如果沒有傳遞fnProgress,就會報出undefined的錯誤 var action=data[1], fn=data[0], returned; if(jQuery.isFunction(fn)){ //通過done,fail,progress添加的方法,只有在對應的回調函數隊列fire的時候才會觸發 deferred[handler](function(){ //這里的this,arguments是調用fire/fireWith時候傳遞 //這里的this可以通過fireWith中指定context,arguments也是fire/fireWith的時候傳遞的參數 returned=fn.apply(this,arguments); //如果函數的返回值依舊是一個異步隊列,則將jQuery.pipe返回的異步隊列的成功,失敗,消息回調添加到返回的retuned對應的回調列表中 if(returned&&jQuery.isFunction(returned.promise)){ returned.promise().then(newDefer.resolve,newDefer.reject,newDefer.notify); }else{ //如果函數返回值不是異步隊列,則jQuery.pipe()返回的異步隊列對應狀態的方法立即觸發 newDefer[action+'With'](this===deferred?newDefer:this,[returned]); } }); }else{ deferred[handler](newDefer[action]); } }); }).promise(); }, //注意promise()和promise({})這兩種寫法是完全不同的,前者返回異步對象的只讀版本,后者返回一個副本 promise:function(obj){ return obj==null?promise:jQuery.extend(obj,promise); }, }, deferred=promise.promise({}), key; //為deferred添加狀態改變的相關函數,與fire,fireWith相對應 for(key in list){ deferred[key]=list[key].fire; deferred[key+'With']=list[key].fireWith; } deferred.done(function(){ state='resolved'; },failList.disable,progressList.disable) .fail(function(){ state='rejected'; },doneList.disable,progressList.disable); if(func){ //這句話決定了,通過jQuery.Deferred(func)調用的時候,func的context和參數 func.call(deferred,deferred); } return deferred; }, When:function(firstParam){ var resolveArgs=core_slice.call(arguments,0),//用來存放成功參數 length=resolveArgs.length, count=length,//維護一個計數器 progressArgs=new Array(length),//用來存放消息參數 i=0, //只有當在只有一個參數,并且該參數是延遲對象的情況下,主延遲對象等于該第一個參數,否則新建一個主延遲對象 deferred=length<=1&&firstParam&&jQuery.isFunction(firstParam.promise)?firstParam:jQuery.Deferred(), promise=deferred.promise(); if(length>1){ for(;i<length;i++){ if(resolveArgs[i]&&jQuery.isFunction(resolveArgs[i].promise)){ resolveArgs[i].then(resolveFunc(i),deferred.reject,progressFunc(i)); }else{ count--; } if(!count){ deferred.resolveWith(deferred,resolveArgs); } } }else if(deferred!==firstParam){//說明只有一個或0個參數,若有一個,該參數還不是延遲對象 //此時立即觸發 deferred.resolveWith(deferred,length?[firstParam]:[]); } //為了將參數i的值傳遞,這里采用閉包 function resolveFunc(i){ //回調函數的參數(即返回函數中的value/arguments)是由fire/fireWith的時候進行參數指定 return function(value){ resolveArgs[i]=arguments.length>1?core_slice.call(arguments):value; //每一次參數延遲對象的resolve觸發,都令count的值減去一 if(!--count){ //如果計算器變為0,那么主延遲對象的resolve方法觸發 deferred.resolveWith(deferred,resolveArgs); } } } function progressFunc(i){ return function(value){ progressArgs[i]=arguments.length>1?core_slice.call(arguments):value; deferred.notifyWith(promise,progressArgs); } } return promise; } });/*********數據緩存模塊****************************************///數據緩存模塊的整體思路//2.0.3版本的jQuery較之于1.7.3版本,使用面向對象的寫法重構了數據緩存Data模塊//數據緩存模塊的整體依據是://data_user和data_priv在一次運行期間只有對應的唯一對象,所有DOM元素的緩存都基于這兩個實例對象完成//data_user與data_priv這兩個Data實例有各自的緩存對象屬性cache,分別用于存儲用戶自定義數據和內部數據//以data_user為例,在向對應的data_user對應的緩存對象cache中保存數據時,會為每個DOM元素分配一個唯一的id,該id作為該DOM元素的附加屬性//該唯一id(初始值為0,之后一次加1)會附加到DOM元素上,對應的DOM元素的屬性名是data_user.expando,其對應的屬性值就是id//同時,會把該id作為屬性名添加到data_user的緩存對象屬性cache中,對應的屬性值是一個都object對象,該對象稱為DOM元素的數據緩存對象,其中存儲著屬性名和屬性值的映射//這樣,通過分配唯一的id把DOM元素和該DOM元素的數據緩存對象關聯起來//data_priv與之類似 var data_priv,data_user, rbrace=/^(?:\{\s\S*\}|\[\s\S*\])$/,//匹配json字符串格式,諸如{},或者[],不用.*進行匹配的原因是.不能匹配換行符 rmultiDash=/([A-Z])/g;//匹配任意的大寫字母 function Data(){ //jQuery.expando是jQuery的靜態屬性,對于jQuery的每次加載運行期間時唯一的 //Math.random生成一個0-1之間的隨機數 this.expando=jQuery.expando+Math.random(); this.cache={}; //這里采用訪問器屬性的寫法 //常用的寫法是Object.defineProperty(對象,對象屬性,{[[get]],[[set]],[[configurable]],}) //這句話的目的,this.cache中的0屬性是個只讀屬性 Object.defineProperty(this.cache,0,{ get:function(){ return {}; } }); } //下面可以看到,只有當accepts為false的時候,返回的id為0 Data.uid=1; Data.accepts=function(owner){ //只有DOM元素,document元素,以及普通的js對象可以操作數據緩存 return owner.nodeType?owner.nodeType===1||owner.nodeType===9:true; }; Data.prototype={ //獲取(設置)owner對應的id,如果沒有,則為其this.expando對應的屬性,值為id,并未其在this.expando中創建緩存對象 key:function(owner){ if(!Data.accepts(owner)){ return 0; } var expando=this.expando, id=owner[expando]; if(!id){ id=Data.uid++; //為owner定義expando屬性,為了保證該屬性不可遍歷且只讀,使用訪問器屬性進行定義 //defineProperty一次只定義一個屬性,接受三個參數,對象,屬性名,屬性描述對象 //defineProperties可以通過描述符一次定義多個屬性,接受兩個參數 //具體用法可以參照講解http://www.tuicool.com/articles/ju26riE Object.defineProperty(owner,expando,{ value:id, }); } if(!this.cache[id]){ this.cache[id]={}; } return id; }, //為DOM元素對應的緩存設置數據 //data參數可以是字符串,也可以是對象/數組,當data是對象/數組的時候,value可以不賦值 set:function(owner,data,value){ var id=this.key(owner), //該DOM元素對應的緩存對象 cache=this.cache[id], key; if(typeof data==='string'){ cache[data]=value; }else{ for(key in data){ cache[key]=data[key]; } } return cache; }, //獲取DOM元素owner對應緩存中屬性key的值 //如果參數key不賦值,則代表去除owner對應的對象緩存 get:function(owner,key){ var id=owner[this.expando], cache; if(!id){ return undefined; } cache=this.cache[id]; return key?cache[key]:cache; }, //設置或獲取 access:function(owner,key,value){ var tmp; if(!key||((key&&typeof key==='string') &&!value)){//說明是獲取 //先嘗試key本身,不行的話嘗試轉駝峰 tmp=this.get(owner,key); return tmp? tmp: this.get(owner,jQuery.camelCase(key)); } //否則說明是設置 this.set(owner,key ,value); return value ? value : key; }, //如果沒有傳入參數key,則移除DOM元素或者javascript元素關聯的所有數據 //如果傳入了參數key,則移除關聯的指定名稱的數據 //如果key是數組或者空格分割的多個數據名,則一次可以刪除多個數據,刪除的時候還需要嘗試camel轉換之后的形式 remove:function(owner,key){ var i,camel,length, id=this.key(owner), cache=this.cache[id]; if(!key){ this.cache[id]={}; }else{ //可能是數組,可能是字符串,該字符串還可能使用空格分開 if(typeof key ==='string'){ key=key.match(rnotwhite);//轉換為數組形式 } key=key.concat(jQuery.map(key,jQuery.camelCase)); for(i=0,length=key.length;i<length;i++){ delete cache[key[i]]; } } }, //返回owner對應的緩存對象是否有值 hasData:function(owner){ return !jQuery.isEmptyObject(this.cache[owner[this.expando]]||{}); }, //刪除Owner對應的緩存對象(注意不是講緩存對象置為空數組) discard:function(owner){ if(owner[this.expando]){ delete this.cache[owner[this.expando]]; } } }; data_user=new Data(); data_priv=new Data(); jQuery.extend({ acceptData:Data.accepts, //同事查看用戶自定義緩存和私有緩存 hasData:function(elem){ return data_user.hasData(elem)||data_priv.hasData(elem); }, //操作用戶自定義數據 data:function(elem,name,data){ return data_user.access(elem,name,data); }, removeData:function(elem,name){ data_user.remove(elem,name); }, _data:function(elem,name,data){ return data_priv.access(elem,name,data); }, _removeData:function(elem,name){ data_priv.remove(elem,name); }, //下面這兩個get方法在jquery源代碼中沒有,這里加上,便于測試 get:function(elem,key){ return data_user.get(elem,key); }, _get:function(elem,key){ return data_priv.get(elem,key); } }); //這部分操作的都是用戶自定義數據 jQuery.fn.extend({ data:function(key,value){ //在這一步中,jQuery的源代碼考慮了 jQuery.each(this,function(){ jQuery.data(this,key,value); //console.log(jQuery.get(this)); //console.log($(this).getData()); }); }, removeData:function(key){ //別忘了,jquery對象是類數組 jQuery.each(this,function(){ // data_user.remove(this,key); jQuery.removeData(this,key); }); }, //下面的getData方法也是為了測試方便加上的,在jQuery源代碼中沒有 getData:function(key){ //下面這種寫法不對,注意結合jQuery.each源代碼看就知道,get到的值并沒有返回 // jQuery.each(this,function(){ // jQuery.get(this,key); // }); //根據jQuery的思路,獲取的時候,獲得的總是首個 return jQuery.get(this[0],key); } }); //jQuery的源代碼中用于處理html5中的data屬性,這里暫不考慮 function dataAttr(elem,key,data){ } //搞清楚構造函數(包括普通函數),構造函數的原型,實例之間的關系 //javascript中每個函數都有一個prototype屬性,指向其原型對象 //原型對象有一個constructor屬性,指向對應的構造函數 //每個實例對象有一個隱形屬性,在chrome中是_proto_,指向原型對象,注意這個屬性是介于實例和原型對象之間的 /************************jQuery隊列Queue模塊**********************************/ //jQuery的隊列模塊基于數據緩存模塊,異步回調和數組實現 //jQuery的隊列為動畫模塊等提供基礎功能,其隊列中的元素都是函數;jQuery將其單獨提取了一個命名空間,說明程序員也可以發揮自己的想法創建出非動畫隊列 //在隊列模塊中,通過數組存儲函數,通過數組方法.push()和.shift()來實現入隊和出隊操作,通過.call來執行函數 //和普通隊列不同的是,除了支持IFIO之外,出隊的函數還可以自動調用,其調用的上下文是DOM元素elem,參數是next,數據緩存中的typeHooks對象,這些在dequeue中被指定 //對于動畫隊列,入隊的動畫函數會自動調用方法.dequeue()出隊并且執行,對于非動畫隊列,則需要手動調用方法.dequeue() //數組作為內部數據存儲在關聯的數據緩存對象,數據名稱為隊列名稱加字符串“queue” //如果沒有傳入隊列名稱,則默認為標準動畫fx; //隊列模塊會在隊列名稱后自動加上后綴queue,表示這是一個隊列 //這部分的數據緩存對象結構是:data-priv——>DOM元素的連續數字ID——>1)typequeue:[隊列函數列表], //2)typequeueHooks:A)empty,對應一個callbacks回調函數,該回調函數在隊列函數列表執行完畢的時候被調用,執行的操作包括:a)清楚緩存typequeue和typequeueHooks; //b)如果還調用了promise,則為hooks的empty對應的回調函數callbacks添加了令監控計數器count減1的操作; //c)如果還調用了delayed,回味typeHooks添加stop屬性,用于終止timeout延時計算器 //隊列模塊的代碼結構 jQuery.extend({ //該方法用于返回或者修改匹配元素關聯的函數隊列,根據傳入參數的不同,函數實現的功能也有所不同 //這是一個低級方法,外部調用的時,應該用.queue替換 //queue(elem,[type])返回匹配元素關聯的函數隊列 //queue(elem,type,newQueue)參數data是函數數組,此時用newQueue替換當前隊列 //queue(elem,type,callback())參數data是函數,此時將callback添加到當前的函數隊列中 queue:function(elem,type,data){ var type=type||'fx'+queue, queue=data_priv.get(elem,type); if(data){ //說明是設置操作 if(!queue||jQuery.isArray(data)){ //必須使用jQuery.makeArray,針對!queue且data是function的情況 data_priv.access(elem,type,jQuery.makeArray(data)); }else{ queue.push(data); } } return queue||[]; }, //jQuery中的隊列不同于隊列定義的是,jQuery的隊列不僅支持函數的入隊和出隊操作,出隊的函數還會自動調用 dequeue:function(elem,type){ var type=type||'fx', queue=jQuery.queue(elem,type), hooks=jQuery._queueHooks(elem,type), startLength=queue.length, fn=queue.shift(), //不能令next=jQuery.dequeue,因為不能指定參數啊啊啊 next=function(){ jQuery.dequeue(elem,type); }; //這個inprogress搞不太懂,回頭結合動畫effects模塊一起看吧 if(fn==='inprogress'){ fn=queue.shift(); startLength--; } if(fn){ //同樣,inprogress搞不懂,看動畫模塊如何讓inprogress出隊吧 if(type==='fx'){ queue.unshift('inprogress'); } //取消hooks上的定時器,這個依舊搞不太懂,結合delay一起看吧 delete hooks.stop; //先不考慮動畫和延時 fn.call(elem,next,hooks); } //注意上面fn.call之后startLength并沒有-1 //測試的結果是,只有在隊列已經為空的情況下,再次調用dequeue進行出隊,才會觸發緩存清除的empty操作 if(!startLength&&hooks){ hooks.empty.fire(); } }, _queueHooks:function(elem,type){ var hookKey=type+'queueHooks'; return data_priv.get(elem,hookKey)||data_priv.access(elem,hookKey,{ empty:jQuery.Callbacks('once memory').add(function(){ data_priv.remove(elem,[type+'queue',hookKey]); console.log('empty call'); }) }); } }); jQuery.fn.extend({ //.queue([queuename]);返回第一個匹配元素關聯的函數隊列 //.queue([queueName],newQueue);修改匹配元素關聯的函數隊列,使用函數數組newQueue替換當前隊列 //.queue([queueName],callback(next,hooks));修改匹配元素關聯的函數隊列,添加callback到隊列中 //如果queueName省略,則默認是動畫隊列fx queue:function(type,data){ var setter=2; if(typeof type!=='string'){ //進行參數修正 data=type; type='fx'; setter--; } //靠,這種判斷是獲取還是設置的點子是怎么想出來的 if(arguments.length<setter){ //說明是獲取操作,根據jQuery的思想,獲取的時候,僅獲取首個 return jQuery.queue(this[0],type); } //否則說明是設置操作,根據jQuery的思想,設置的時候,進行遍歷設置 if(data){ this.each(function(i,item){ var queue=jQuery.queue(this,type,data); //確定添加了hooks jQuery._queueHooks(this,type); //如果是動畫隊列,那么首次入隊的時候回自動出隊執行,不必手動調用dequeue,唉,這點結合動畫模塊來看吧 if(type==='fx'&&queue[0]!=='inprogress'){ jQuery.dequeue(this,type); } }); } }, dequeue:function(type){ this.each(function(){ jQuery.dequeue(this,type); }); }, //使得隊列中下一個函數延遲執行 delay:function(time,type){ time=jQuery.fx?jQuery.fx.speeds[time]||time:time; type=type||'fx'; //next和hooks的參數賦值是在dequeue的fn.call中,還記得么? return this.queue(type,function(next,hooks){ var timerId=setTimeout(next,time); hooks.stop=function(){ clearTimeout(timerId); } }); }, clearQueue:function(type){ this.queue(type||'fx',[]); }, //針對每一個匹配元素,對其添加監控,當所有匹配元素的type隊列中的函數都執行完畢時,調用Promise的done添加的成功回調函數 promise:function(type,obj){ var elems=this, count=0, i=elems.length, defered=jQuery.Deferred(), hook; if(typeof type!=='string'){ obj=type; type=undefined; } type=type||'fx'; function resolve(){ if(!(--count)){ //如果計數器count變為0 defered.resolveWith(elems,[elems]); } } //添加監控 while(i--){ hook=elems[i]&&data_priv.get(elems[i],type+'queueHooks'); if(hook&&hook.empty){ count++; hook.empty.add(resolve); } } //這里為毛要調用一次呢? // resolve(); return defered.promise(obj); } }); window.jQuery=window.$=jQuery; })(window);
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com