一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

jquery1.43源碼分析之核心部分

 quasiceo 2012-11-22
基于jquery-1.4.3rc1版本的. 正式版據(jù)說過幾天就發(fā)布, 應(yīng)該差別不大.

這個系列應(yīng)該有十章, 本來準(zhǔn)備寫完了一起發(fā)的. 但有幾章還不知道會拖到什么時候. 現(xiàn)在完成大概五章的內(nèi)容了.

會陸續(xù)放上來.

這個源碼分析系列我不想它成為單純的翻譯注釋.除了那種一眼就明白的代碼, 其它基本都加了注解.
有時候一句代碼的分析可能會關(guān)聯(lián)到一個重要的知識點, 我也盡量在能力范圍內(nèi)把它講清. 看源碼前盡可能的多思考一下,假如是我們實現(xiàn)這個方法,會怎么去做. 再對比jquery的實現(xiàn)就更能加深理解. 另外你也可以對jquery的某些代碼保持懷疑, 每次版本更新,都會修復(fù)很多bug, 可能你現(xiàn)在懷疑的就是其中一個.

這些源碼分析里面的錯誤和缺陷肯定是有的. 寫成文檔的目的一是為了梳理自己的知識,再就是希望在和大家的討論中, 能認識和改正自己的錯誤.

最后面提供了pdf下載.








------------------------------- 分割線-----------------------------------------------




jquery核心


一  構(gòu)造jquery.

相對于其它庫里傳統(tǒng)的構(gòu)造對象方法. jquery提供了一種截然不同的方法. 它選擇創(chuàng)造一個全新的奇異世界.

首先所有的jquery代碼被一個自動執(zhí)行的閉包包裹起來, 只在后面暴露$和jQuery這2個變量給外界 盡量避開變量沖突.
Java代碼  收藏代碼
  1. (function(window,  undefined){  
  2. …..  
  3. })(window)  

window和undefined都是為了減少變量查找所經(jīng)過的scope. 當(dāng)window通過傳遞給閉包內(nèi)部之后, 在閉包內(nèi)部使用它的時候,
可以把它當(dāng)成一個局部變量, 顯然比原先在window scope下查找的時候要快一些.
undefined也是同樣的道理, 其實這個undefined并不是javascript數(shù)據(jù)類型六君子之一的undefined, 而是一個普普通通的變量名.
只是因為沒給它傳遞值. 它的值就是undefined.  undefined并不是javascript的保留字.

然后是一個套子套住jquery的構(gòu)造方法
Java代碼  收藏代碼
  1. var jQuery = (function(){   --------------------jq1  
  2.     var jQuery = function( selector, context ){    ---------------jq2  
  3.         ……..  
  4. };  
  5.     return (window.jQuery = window.$ = jQuery);  
  6. })()  


首先定義jq1, 這個jQuery最終在return  (window.jQuery = window.$ = jQuery)的時候會變成
window下面的變量供外界使用.
而jq2供jquery內(nèi)部執(zhí)行的時候調(diào)用. 最終作為jq1的引用返回.
return (window.jQuery = window.$ = jQuery);這句話等價于
Java代碼  收藏代碼
  1. window.jQuery = window.$ = jQuery;  
  2. return window. jQuery.  



現(xiàn)在來看看jquery對象是怎么被創(chuàng)建出來的. jquery作為一個獨立特行的庫,  它產(chǎn)生jquery對象時并不需要用new 操作符..
它寧愿選擇這種方式, 比如要產(chǎn)生一個構(gòu)造函數(shù)Man的對象.
Java代碼  收藏代碼
  1. function Man(name){  
  2.     this.name = name;     
  3. }  
  4.   
  5. function People(name){  
  6.     return new Man(name);  
  7. }  
  8.   
  9. var pe = People("zengtan");  
  10. alert (pe.name);  

同樣真正作為jQuery對象的構(gòu)造方法的并不是
function (selector, context){
}
而是jQuery.fn.init.
Java代碼  收藏代碼
  1. var jQuery  =  function( selector, context ) {            
  2.         return new jQuery.fn.init( selector, context );  
  3.     }  

jQuery.fn就是jQuery.prototype. 見源碼102行.
jQuery.fn = jQuery.prototype = {}

init是掛在jQuery.prototype上的屬性.
當(dāng)jQuery(‘div’)的時候, 實際上轉(zhuǎn)交給了jQuery.fn.init構(gòu)造函數(shù)來生成對象.
當(dāng)然我們想用new jQuery來生成jquery對象也可以. 跟直接用jQuery()沒區(qū)別.
因為構(gòu)造函數(shù)一定會返回一個對象.如果顯示指定了返回某個對象.就會返回那個對象,
否則才會返回this對象.
好比說, 有只母雞被你強迫下一個蛋, 它會先看窩里有沒有別人的蛋, 如果沒有,才會自己努力下一個.
這里顯然返回的是jQuery.fn.init的對象.

也許現(xiàn)在你開始回憶制作jquery插件時, 明明是給jQuery.prototype添加方法.
這里返回的又是jQuery.prototype.init的對象.
原來在源碼333行,  jQuery.prototype.init.prototype = jQuery. prototype;
現(xiàn)在很容易看明白. 給jQuery.prototype添加方法就等于給jQuery. prototype.init.prototype添加方法了.
JQuery api里的方法大部分都是通過jQuery.prototype擴展上去的, 除此之外. 我們還要給jquery對象加上索引. 給集合添加length屬性,讓他們更像一個數(shù)組里的元素.

搞明白這些, 再來看jQuery. prototype.init這個方法里究竟是怎樣生產(chǎn)jquery對象的.
我們可以把jQuery. prototype.init想象成一個火腿腸加工器. 只要你放了正確的原料進去,
它就可以把原料變成火腿腸生產(chǎn)出來.如果你不小心放錯了原料.它也會幫你變成火腿腸. 不過只有塑料包裝, 里面沒有火腿.
當(dāng)然這個加工器里面的構(gòu)造是很復(fù)雜的, 它需要判斷材料種類, 數(shù)量等等.
一般這個材料主要為這4種情況
1 dom節(jié)點
2 字符串
3 函數(shù)
4 數(shù)組
5 其他元素

一 jQuery構(gòu)造方法
jQuery的構(gòu)造方法會生成一組jquery對象的集合.具體關(guān)于init方法的分析, 還是留在選擇器部分說吧.

二 jQuery對象訪問

jquery構(gòu)造完對象之后, 會提供一些方法訪問這些對象.

1  jQuery.prototype.size 
集合內(nèi)元素的數(shù)量
就是通過this.length得到.

2  jQuery.prototype.get 
按照索引取得集合內(nèi)某個元素, 返回包裝前的原始對象
Java代碼  收藏代碼
  1. get: function( num ) {  
  2.         return num == null ?   
  3.  //如果參數(shù)為null或者undefiend. 注意是==.  
  4.             this.toArray() :  //如果不傳參數(shù), 集合內(nèi)的元素全部轉(zhuǎn)化為一個數(shù)組(1)  
  5.             ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] );   
  6. //調(diào)用silce方法截取某一個.可以傳入負數(shù)并且返回的是包裝前的對象.  
  7.     }  
  8.   
  9. (1) 看看這里的this.toArray函數(shù).  
  10. toArray: function() {  
  11.         return slice.call( this0 );  
  12.     }  

很簡單, 就是讓當(dāng)前jquery對象冒充Array的對象, 調(diào)用Array.prototype.slice進行截斷. 返回的是一個數(shù)組.
至于為什么可以像這樣使用對象冒充. 我們抽個地方來好好討論一下.
其實如果查看v8之類開源引擎的源碼就知道(當(dāng)然也可以在ecma里掙扎一番). 要調(diào)用Array原型鏈上的方法. 通常這個對象滿足2個條件就可以了.
1, 本身可以存取屬性.
2, length屬性不能是只讀(可以沒有l(wèi)ength屬性).
由于Array.prototype.slice方法太長了. 我拿Array.prototype.push方法舉例. 在V8的src目錄下的array.js可以找到這些方法.
比如push
Java代碼  收藏代碼
  1. function ArrayPush() {  
  2.   var n = ToUint32(this.length);  
  3.  /* 
  4.     對象/數(shù)組本身的length. 如果為null或者undefined, 會在ToUint32中轉(zhuǎn)化為0. 所以 
  5.     即使沒有l(wèi)ength屬性,也會給一個默認的0值. 
  6.  */  
  7.   var m = %_ArgumentsLength();    //參數(shù)的length.  
  8.   for (var i = 0; i < m; i++) {  
  9.     this[i+n] = %_Arguments(i);   //復(fù)制屬性  
  10.   }  
  11.   this.length = n + m;            //重設(shè)length.  
  12.   return this.length;  
  13. }  

可以看到push操作的核心就是復(fù)制屬性和重設(shè)長度.
jquery對象完全可以滿足這2個條件. 同樣的道理 一個對象字面量{}也可以.
而string類型的不可以, 因為不能在string上存取屬性. function對象雖然可以存取屬性, 也有l(wèi)ength屬性.
不過它的length屬性比較特殊, 表示形參的個數(shù), 是一個只讀屬性, 源碼中的this.length = n + m這一句不起作用, 所以function對象也不行. 同理window對象也不行.
上面的slice.call也是這個原理, 雖然slice方法的實現(xiàn)更復(fù)雜一點.

明白了這個,我們可以解釋很多奇怪的問題.比如:
Java代碼  收藏代碼
  1. var a = {};  
  2. a[0] = 1;  
  3. a[1] = 2;  
  4. Array.prototype.push(a, 3);  
  5. alert (a.length)   // a沒有l(wèi)ength屬性, 默認的給0值, 所以結(jié)果為1 .  

如果在push操作之前添加一句 a.length = 2;
再進行push操作后, a.length就為3了.



3 jQuery.prototype.index 
搜索匹配的元素,并返回相應(yīng)元素的索引值,從0開始計數(shù)。
如果不給 .index() 方法傳遞參數(shù),那么返回值就是這個jQuery對象集合中第一個元素相對于其同輩元素的位置。
如果參數(shù)是一組DOM元素或者jQuery對象,那么返回值就是傳遞的元素相對于原先集合的位置。
如果參數(shù)是一個選擇器,那么返回值就是原先元素相對于選擇器匹配元素中的位置。如果找不到匹配的元素,則返回-1。
沒有什么特別需要解釋的, 直接看代碼.
Java代碼  收藏代碼
  1. index: function( elem ) {  
  2.         if ( !elem || typeof elem === "string" ) {     
  3.         //如果沒有參數(shù), 或者參數(shù)是選擇器.  
  4.             return jQuery.inArray( this[0],  
  5.                 elem ? jQuery( elem ) : this.parent().children() );  
  6.         //如果有參數(shù)(選擇器), 則查找元素本身在這些選擇器組成的jq對象集合中的位置  
  7.         //如果沒有參數(shù), 查找元素本身在它的兄弟節(jié)點之間的位置. this.parent().children()可以取得自己和全部兄弟節(jié)點.  
  8. }  
  9.         return jQuery.inArray(  
  10.             //參數(shù)是一個對象  
  11.             elem.jquery ? elem[0] : elem, this );  
  12.             //如果是jquery對象, 取得它的原始節(jié)點. 再查找在當(dāng)前集合中的位置     
  13. }  

顧名思義jQuery.inArray就是判斷數(shù)組里有沒有某個元素.當(dāng)然這里的數(shù)組也包括偽數(shù)組.
這個方法雖然實現(xiàn)起來很簡單, 關(guān)于inArray這個名字在jquery的官方論壇卻有頗多爭議.
很多人認為它應(yīng)該返回true或者false, 而不是索引的位置.
john resig只是說暫時還不準(zhǔn)備修改這個方法.

有些瀏覽器還不支持Array.prototype.indexOf方法. 所以首先在源碼的851行, 有這樣一段代碼.
Java代碼  收藏代碼
  1. if ( Array.prototype.indexOf ) {  
  2.     jQuery.inArray = function( elem, array ) {  
  3.         return indexOf.call( array, elem );  
  4.     };  
  5. }  

如果支持Array.prototype.indexOf. 則重寫jQuery.inArray, 直接用Array.prototype.indexOf.call(array, elem );
在頁面加載的時候就重寫這個方法. 也避免了在函數(shù)里反復(fù)判斷造成的浪費.
然后
Java代碼  收藏代碼
  1. inArray: function( elem, array ) {  
  2.         if ( array.indexOf ) {  
  3. //確認indexOf方法存在.或防止indexOf方法被改寫.  
  4.             return array.indexOf( elem );  
  5.         }  
  6.         for ( var i = 0, length = array.length; i < length; i++ ) {  
  7.             //否則遍歷數(shù)組, 返回正確的索引.  
  8. if ( array[ i ] === elem ) {  
  9.                 return i;  
  10.             }  
  11.         }  
  12.         return -1;  //如果數(shù)組里沒有這個元素, 返回-1.  
  13.     }  

三 數(shù)據(jù)緩存

jQuery.data

在實際應(yīng)用中, 我們經(jīng)常需要往節(jié)點中緩存一些數(shù)據(jù). 這些數(shù)據(jù)往往和dom元素緊密相關(guān). dom節(jié)點也是對象, 所以我們可以直接擴展dom節(jié)點的屬性.
不過肆意污染dom節(jié)點是不良少年的行為. 我們需要一種低耦合的方式讓dom和緩存數(shù)據(jù)能夠聯(lián)系起來.

jquery提供了一套非常巧妙的緩存辦法.

我們先在jquery內(nèi)部創(chuàng)建一個cache對象{}, 來保存緩存數(shù)據(jù).

然后往需要進行緩存的dom節(jié)點上擴展一個值為jQuery.expando的屬性, 這里是”jquery” + (new Date).getTime().

接著把每個節(jié)點的dom[jQuery.expando]的值都設(shè)為一個自增的變量id,保持全局唯一性.
這個id的值就作為cache的key用來關(guān)聯(lián)dom節(jié)點和數(shù)據(jù).
也就是說cache[id]就取到了這個節(jié)點上的所有緩存.

而每個元素的所有緩存都被放到了一個map里面,這樣可以同時緩存多個數(shù)據(jù).

比如有2個節(jié)點dom1和dom2, 它們的緩存數(shù)據(jù)在cache中的格式應(yīng)該是這樣
Java代碼  收藏代碼
  1. cache = {  
  2.     dom1[  jQuery.expando ]: {  
  3.         key1: value1,  
  4.         key2: value2  
  5. },  
  6. dom2[ jQuery.expando ] {  
  7.     key3: value3,  
  8.     key4: value4  
  9. }  
  10. }  


jQuery.expando的值等于”jquery”+當(dāng)前時間, 元素本身具有這種屬性而起沖突的情況是微乎其微的.

我們在看源碼之前, 先根據(jù)上面的原理來自己實現(xiàn)一個簡單的緩存系統(tǒng).以便增強理解.

先把跟data相關(guān)的所有代碼都封裝到一個閉包里,通過返回的接口暴露給外界.
同時為了簡便,我們拆分成setData和getData兩個方法.
Java代碼  收藏代碼
  1. <div id="ddd">dddddddd</div>  
  2.   
  3. <script>  
  4.   
  5. var Data = function(){  
  6.     var cache = {};  
  7.     var expando = "zengtan" + +new Date;  
  8.     var uuid = 1;  
  9.       
  10.     var setData = function(elem, key, value){  
  11.             var id = elem[expando];  
  12.             if (!id){   //第一次給元素設(shè)置緩存  
  13.                 id = ++uuid;  
  14.                 elem[expando] = id;  
  15.             }  
  16.             if (!cache[id]){   //這個元素第一次進行緩存或者緩存已被清空  
  17.                 cache[id] = {};  
  18.             }  
  19.             cache[id][key] = value;  
  20.     };  
  21.   
  22.     var getData = function(elem, key){  
  23.         var id = elem[expando];  //取得cache里跟dom節(jié)點關(guān)聯(lián)的key  
  24.         return cache[id] && cache[id][key] || null;  //如果緩存里沒有, 返回null  
  25.     }  
  26.   
  27.     return {  
  28.         setData: setData,  
  29.         getData: getData      
  30.     }  
  31. }()  
  32.   
  33. </script>  
  34.   
  35. var div = document.getElementById("ddd");  
  36. Data.setData(div, "name""zengtan");  
  37. var value = Data.getData(div, "name");  
  38. alert (value)  

看看源碼實現(xiàn).

首先聲明一些特殊的節(jié)點, 在它們身上存屬性的時候可能會拋出異常.
Java代碼  收藏代碼
  1. noData: {  
  2.         "embed"true,  
  3.         // Ban all objects except for Flash (which handle expandos)  
  4.         "object""clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",  
  5.         "applet"true  
  6.     }  


這個對象里的數(shù)據(jù)用在acceptData方法中, 跟1.42版本相比, 這里多了對什么flash的object的特殊處理.
總之a(chǎn)cceptData方法就是判斷節(jié)點能否添加緩存.

看具體的jQuery.data
Java代碼  收藏代碼
  1. data: function( elem, name, data ) {  
  2.     if ( !jQuery.acceptData( elem ) ) {  
  3.         return;  
  4.     }  
  5.     //noData類型  
  6.     elem = elem == window ?  
  7.         windowData :  
  8.         elem;  
  9.     //處理elem是window的情況, 如果不單獨處理的話, 等于增加了一個全局變量, windowData也是一個{}  
  10.     var isNode = elem.nodeType,  
  11.     //判斷是不是dom節(jié)點,由于非dom節(jié)點的緩存在繼承和內(nèi)存泄露上都會造成麻煩,1.43版本已經(jīng)把dom節(jié)點和其他對象分開處理了.  
  12.         id = isNode ? elem[ jQuery.expando ] : null,  
  13.         cache = jQuery.cache, thisCache;  
  14.     //因為存數(shù)據(jù)的時候, 會給elem[ jQuery.expando ]設(shè)置一個全局唯一標(biāo)志量. 判斷id是否為undefined, 就知道已經(jīng)有沒有往這個元素上緩存過數(shù)據(jù).   
  15.   
  16.     if ( isNode && !id && typeof name === "string" && data === undefined ) {  
  17.     //如果是dom節(jié)點對象, 并且現(xiàn)在是get方式(typeof name === "string" && data === undefined表示get方式), 又沒有緩存(!id表示沒有緩存).  
  18.         return;  
  19.     }  
  20.   
  21.     if ( !isNode ) {  
  22.         cache = elem;  
  23.     //如果是非dom節(jié)點對象, 取緩存里的屬性就直接取元素本身屬性.  
  24.     } else if ( !id ) {  
  25.         elem[ jQuery.expando ] = id = ++jQuery.uuid;  
  26.     //第一次進行緩存, 分配一個全局唯一標(biāo)志id.  
  27.     }  
  28.   
  29.     if ( typeof name === "object" ) {  
  30.     //如果key是對象類型  
  31.         if ( isNode ) {  
  32.             cache[ id ] = jQuery.extend(cache[ id ], name);  
  33.     /* 
  34.     把整個對象都復(fù)制到原來的緩存上, 比如 
  35.          $('#ddd').data({ 
  36.                 "v2": "bbb", 
  37.                 "v3": "ccc" 
  38.             }); 
  39.             相當(dāng)于$('#ddd').data("v2", "bbb").data("v3":"ccc"); 
  40.             1.42版本用的cache[ id ] = jQuery.extend(true, {}, name). 這樣會清空以前的緩存. 
  41.         */  
  42.         } else {  
  43.             jQuery.extend( cache, name );  
  44.         //如果不是dom節(jié)點. 把屬性復(fù)制到元素本身.  
  45.         }  
  46.   
  47.     } else if ( isNode && !cache[ id ] ) {  
  48.         //如果cache[ id ]中沒有東西, 表示這個元素第一次進行緩存或者緩存已被清空, 設(shè)置cache[ id ]為一個新的map.  
  49.         cache[ id ] = {};  
  50.     }  
  51.   
  52.     thisCache = isNode ? cache[ id ] : cache;  
  53.   
  54.     if ( data !== undefined ) {  
  55.         /* 
  56.             set操作, 也可以防止一些意外的情況下緩存被清空. 比如data未定義的情況下, 緩存操作是無效的. 
  57.             var a = {}; 
  58.             var b; 
  59.             $(a).data("c", 3); 
  60.             $(a).data("c", b); b為undefined. 這句是無效的. 要移除緩存可以用removeData方法. 
  61.         */  
  62.         thisCache[ name ] = data;  
  63.         //即cache[ id ][ name ] = data, 把data設(shè)置進cache緩存對象中, 前面分配的自增id當(dāng)做key來關(guān)聯(lián)  
  64.     }  
  65.   
  66.     return typeof name === "string" ? thisCache[ name ] : thisCache;  
  67.     //如果key是string類型, 返回key對應(yīng)的緩存, 否則返回整個元素上的緩存  
  68. }  

從新版本的源碼里可以看到,  1.42版本中data方法的幾個缺點已經(jīng)被解決了.

當(dāng)然我們用jquery緩存系統(tǒng)的時候, 一般調(diào)用的是prototype方法, prototype方法除了調(diào)用上面的靜態(tài)方法之外. 還加入了對節(jié)點上自定義事件的處理, 留在event部分再講.

當(dāng)然, 我們還需要刪除緩存的方法. 現(xiàn)在看看removeData的代碼
Java代碼  收藏代碼
  1. Jquery.removeData  
  2.   
  3.     removeData: function( elem, name ) {  
  4.         if ( !jQuery.acceptData( elem ) ) {  
  5.             return;  
  6.         }  
  7.   
  8.         elem = elem == window ?  
  9.             windowData :  
  10.             elem;  
  11.   
  12.         var isNode = elem.nodeType,  
  13.             id = isNode ? elem[ jQuery.expando ] : elem,  
  14.             //id是該元素在cache上面的key, 如果不是節(jié)點就返回元素本身.  
  15.             cache = jQuery.cache,  
  16.             thisCache = isNode ? cache[ id ] : id;  
  17.   
  18.         if ( name ) {  
  19.             if ( thisCache ) {  
  20.                 delete thisCache[ name ];  
  21.                 //刪除key對應(yīng)的緩存數(shù)據(jù)  
  22.                 if ( isNode && jQuery.isEmptyObject(thisCache) ){  
  23.                     //如果元素是dom節(jié)點并且緩存為一個空對象.說明所有緩存的數(shù)據(jù)都已經(jīng)被刪掉了.  
  24.                     //那么重新調(diào)用一次removeData方法, 刪掉緩存系統(tǒng)里已經(jīng)無用的東西, 防止內(nèi)存泄露, 注意現(xiàn)在走的是下面else分支.   
  25.                     jQuery.removeData( elem );   
  26.                 }  
  27.             }  
  28.         } else {  
  29.             if ( isNode && jQuery.support.deleteExpando ) {  
  30.                 //如果支持delete, 見特性檢測部分.  
  31.                 delete elem[ jQuery.expando ];  
  32.                 //刪掉元素的jQuery.expando屬性  
  33.   
  34.             } else if ( elem.removeAttribute ) {  
  35.                 //如果支持removeAttribute  
  36.                 elem.removeAttribute( jQuery.expando );  
  37.   
  38.             } else if ( isNode ) {  
  39.                 delete cache[ id ];  
  40.                 //如果是dom節(jié)點, 全局緩存里刪除以這個id為key的對象.  
  41.             } else {  
  42.                 for ( var n in elem ) {  
  43.                     //如果是其它對象, 刪除對象的所有屬性. 防止內(nèi)存泄露.  
  44.                     delete elem[ n ];  
  45.                 }  
  46.             }  
  47.         }  
  48.     }  

四 隊列控制
隊列控制也是jquery中很有特點的一個功能. 可以用來管理動畫或者事件等的執(zhí)行順序. queue和dequeue主要為動畫服務(wù).
比如在jquery的動畫里, 因為javascript的單線程異步機制, 如果要管理一批動畫的執(zhí)行順序, 而不是讓它們一起在屏幕上飛舞.
一般我們是一個一個的把下個動畫寫在上個動畫的回調(diào)函數(shù)中, 意味著如果要讓十個動畫按次序執(zhí)行. 至少要寫9個回調(diào)函數(shù).
好吧我承認我做過這樣的事, 直接導(dǎo)致我的視力從5.2變?yōu)?.1.

現(xiàn)在有了隊列機制, 可以把動畫都放到隊列中依次執(zhí)行.究竟怎樣把動畫填充進隊列.用的是queue方法.
不過queue非常先進的是.把動畫push進隊列之后,還會自動去執(zhí)行隊列里的第一個函數(shù).
隊列機制里面的另外一個重要方法是dequeue, 意為取出隊列的第一個函數(shù)并執(zhí)行.此時隊列里面的函數(shù)個數(shù)變?yōu)镹-1.

看個例子.比如我要讓2個div以動畫效果交替隱藏和顯示.同時只能有一個div在進行動畫.
Java代碼  收藏代碼
  1. <style type="text/css">   
  2. #div1 {background:#aaa;width:188px;height:188px;position:absolute;top:10px;left: 110px;}  
  3. #div2 {background:#aaa;width:188px;height:188px;position:absolute;top:310px;left: 110px;}  
  4. </style>  
  5.   
  6. <body>   
  7. <div id="div1">我是一個div</div>  
  8. <div id="div2">我是另一個div</div>  
  9. </body>  
  10.   
  11. 用queue可以這樣做.  
  12. <script type="text/javascript">  
  13. $(document).queue([  
  14.     function(fn){  
  15.         $("#div1").hide(1000, fn);   
  16.       //fn === $(document).dequeue;  
  17.     },  
  18.     function(fn){  
  19.         $("#div2").hide(1000, fn);  
  20.     },  
  21.     function(fn){  
  22.         $("#div1").show(1000, fn);  
  23.     },  
  24.     function(fn){  
  25.         $("#div2").show(1000, fn);  
  26.     }  
  27. ])  
  28. </script>  

首先我們需要一個載體, 來保存這個隊列, 這里選擇了document. 其實選什么節(jié)點都一樣, 保存隊列其實也是一個jQuery.data操作.
然后queue的參數(shù)是一個數(shù)組. 里面的函數(shù)就是隊列依次執(zhí)行的函數(shù). 前面講到, queue方法會自動把隊列里的第一個函數(shù)取出來執(zhí)行.
意味著這些代碼寫完后, div1已經(jīng)開始漸漸隱藏了. 隱藏完畢后, 如果要讓后面的動畫繼續(xù)執(zhí)行, 還要用$(document).dequeue()繼續(xù)取出并執(zhí)行現(xiàn)在隊列里的第一個函數(shù).
當(dāng)然這個操作是放在第一個動畫的回調(diào)函數(shù)里, 以此類推, 第二個.dequeue()要放在第二個動畫的回調(diào)函數(shù)里.
我們看到這里沒有用$(document).dequeue(). 因為這句代碼太長. 注意隊列函數(shù)里有一個參數(shù)fn, fn是dequeue方法內(nèi)部傳給此函數(shù)的, 就是$(document).dequeue().

在看源碼之前, 先自己來想想這個功能應(yīng)該怎么實現(xiàn).
首先我們需要一個數(shù)組, 里面存放那些動畫. 然后需要2個方法, set和get.可以存取動畫.
可能我們還需要一個變量來模擬線程鎖, 保證隊列里的函數(shù)不會同時被執(zhí)行.
最后我們要把這個數(shù)組存入dom的緩存中, 方便隨時存取和刪除.

看源碼, 先是prototype方法.
Java代碼  收藏代碼
  1. jQuery.prototype.queue = function( type, data ) {  
  2.         if ( typeof type !== "string" ) {  
  3.             data = type;  
  4.             type = "fx";    
  5. //修正type, 默認為表示jquery動畫的fx, 如果不為"fx", 即為自己的自定義動畫, 一般我們用"fx"就足夠了.  
  6.         }  
  7.         if ( data === undefined ) {   
  8.             return jQuery.queue( this[0], type );    
  9. //get隊列. 不論集合中是單個或者多個元素, 總是返回第一個元素的隊列  
  10.         }  
  11.         return this.each(function( i, elem ) {    
  12. //set隊列, 遍歷集合內(nèi)所有元素  
  13.             var queue = jQuery.queue( this, type, data );    
  14. //set并返回隊列  
  15.             if ( type === "fx" && queue[0] !== "inprogress" ) {  
  16.          //防止在執(zhí)行函數(shù)的時候, 這里又進行dequeue操作, 這樣會同時執(zhí)行2個函數(shù), 隊列就不受控制了.  
  17.                 jQuery.dequeue( this, type );      
  18. //如果隊列沒有被鎖住, 即此時沒有在執(zhí)行dequeue. 移出隊列里第一個函數(shù)并執(zhí)行它.  
  19. //畢竟queue的主要任務(wù)是添加隊列, 執(zhí)行函數(shù)的操作上, 只能當(dāng)二等公民.  
  20.             }  
  21.         });  
  22.     }  
  23. 看看jQuery.queue這個靜態(tài)方法.  
  24.     jQuery.queue = function( elem, type, data ) {  
  25.         if ( !elem ) {  
  26.             return;  
  27.         }  
  28.         type = (type || "fx") + "queue";  
  29.         var q = jQuery.data( elem, type );  
  30.         //取得元素緩存上面的隊列  
  31.         if ( !data ) {    
  32.         //如果當(dāng)data為空, 只是查詢queue. 那么返回隊列或者[]  
  33. return q || [];  
  34.         }  
  35.         if ( !q || jQuery.isArray(data) ) {  
  36.             q = jQuery.data( elem, type, jQuery.makeArray(data) );   
  37.         //否則是set操作,注意!q || jQuery.isArray(data)這句代碼的含義,   
  38. //如果q為null, 說明緩存上沒有隊列. 此時哪怕隊列里只有一個函數(shù), 也把它轉(zhuǎn)成數(shù)組, 保證隊列是數(shù)組形式  
  39.         //如果q不為null, 說明緩存上已經(jīng)存在隊列, 但是如果現(xiàn)在被添加的函數(shù)是一個數(shù)組的話, 以現(xiàn)在的數(shù)組來代替原來的隊列, 即把原來的隊列清空了.  
  40.         // clearQueue函數(shù)就是用的這一句代碼執(zhí)行清空隊列操作.     
  41. else {  
  42.             q.push( data );  
  43.         //緩存上有隊列并且只添加一個函數(shù)  
  44.         }  
  45.         return q;  
  46.     }  
  47.   
  48. 再看dequeue, dequeue的原型方法什么也沒做, 直接把參數(shù)交給jQuery.dequeue來處理.  
  49.   
  50.     dequeue: function( elem, type ) {  
  51.         type = type || "fx";  
  52.         var queue = jQuery.queue( elem, type ), fn = queue.shift();  
  53.         //取得隊列, 和隊列里的第一個元素, 這里直接給shift掉了, 先斬后奏.  
  54.         if ( fn === "inprogress" ) {  
  55.         //如果fn===” inprogress”, 說明殺錯了人, 我要找的是函數(shù)  
  56.         fn = queue.shift();  
  57.         //繼續(xù)找下一個, 下一個肯定是函數(shù)了.  
  58.         }  
  59.         if ( fn ) {  
  60.             if ( type === "fx" ) {  
  61.                 queue.unshift("inprogress");  
  62.         //函數(shù)執(zhí)行前, 在queue數(shù)組的最前面添加一個進程鎖, 復(fù)活剛才殺錯的人.   
  63. }  
  64.   
  65.             fn.call(elem, function() {  
  66.         //執(zhí)行函數(shù), 參數(shù)就是前面的fn  
  67.                 jQuery.dequeue(elem, type);  
  68.             });  
  69.         }  
  70.     }  
  71. })  

“inprogress”進程鎖是這樣工作的:
如果是dequeue操作, 去掉鎖, 執(zhí)行隊列里的函數(shù), 同時給隊列加上鎖. 如果是queue操作, 要看鎖的狀態(tài), 如果被鎖上了, 就只執(zhí)行隊列的添加操作. 不再調(diào)用dequeue.
其實dequeue和queue都可以執(zhí)行隊列里的第一個函數(shù).queue操作添加完隊列之后, 會調(diào)用dequeue方法去執(zhí)行函數(shù).
但是用dequeue執(zhí)行函數(shù)的時候, 這時候如果又用queue觸發(fā)dequeue的話, 很可能同時有2個函數(shù)在執(zhí)行. 隊列就失去一大半意義了(還是可以保證順序, 但是2個動畫會同時執(zhí)行).
不過這個鎖只能保證在dequeue的時候, 不被queue操作意外的破壞隊列.
如果人為的同時用2個dequeue, 還是會破壞動畫效果的. 所以要把fn寫在回調(diào)函數(shù)里.

清空隊列
Java代碼  收藏代碼
  1. clearQueue: function( type ) {  
  2.         return this.queue( type || "fx", [] );  
  3.     }  

原理上面已經(jīng)提到過了, 就是這一句

Java代碼  收藏代碼
  1. if ( !q || jQuery.isArray(data) ) {  
  2.             q = jQuery.data( elem, type, jQuery.makeArray(data) );   
  3. }  

用一個空數(shù)組代替了原來的隊列.

五  多庫共存
jQuery.noConflict
將變量$的控制權(quán)讓渡給上一個實現(xiàn)它的那個庫.
可能很多人都被多庫共存時的$變量問題困擾過. 比如你先引入了prototype庫,然后又引入了jquery.因為jquery的$會覆蓋prototype的$.
現(xiàn)在想調(diào)用prototype的$怎么辦呢, noConflict方法就派上用場了.
Jquery代碼里最開始就有一句_$ = window.$ , 在加載的時候就用_$來引用原來的$ (比如現(xiàn)在就是prototype庫里的$).
Java代碼  收藏代碼
  1. jQuery.extend({     
  2.     noConflict: function( deep ) {  
  3.         window.$ = _$;     
  4. //window.$歸還給原來的庫. 要回歸jquery再用window.$ = jQuery就可以了.  
  5.         if ( deep ) {      
  6.             window.jQuery = _jQuery;     
  7. //如果deep為true, jQuery這個變量也可以歸還, 不過一般不需要也不推薦這么做.  
  8.         }  
  9.         return jQuery;  
  10.     }  

繼承和拷貝
jQuery.prototype.extend和jQuery.extend
擴展 jQuery 元素集來提供新的方法, 或者把一個對象的屬性拷貝到另外一個對象上.
通常javascript的繼承實現(xiàn)有4種方式.
1 構(gòu)造繼承
2 prototype繼承
3 屬性拷貝
4 實例繼承(這個有點特殊, 主要用于繼承內(nèi)置對象)

這4種繼承都有各自的優(yōu)點和缺陷.
構(gòu)造繼承查找屬性快.但無法繼承原型鏈上的方法,而且每次都要為屬性復(fù)制一份新的副本進行儲存
原型繼承在性能方面好一些,可以共享原型鏈.但查找屬性慢,因為可能要遍歷N條原型鏈才能找到某個屬性.而且原型鏈太多,會使得結(jié)構(gòu)越加混亂.并且會丟失對象的constructor屬性
(對象的constructor總是指向原型鏈最上層的構(gòu)造器)
屬性拷貝非常靈活,但明顯效率偏低. 而且僅僅只是模擬繼承.
實例繼承主要用來繼承Array, Date等內(nèi)置對象, 用的較少.

jquery里采用的是屬性拷貝.其實用哪種繼承是個見仁見智的問題. 也有其它一些庫就是用的原型繼承.

前面也可以注意到, jquery只在構(gòu)造方法或者原型鏈上定義了少量的核心方法. 其它功能塊都是通過extend函數(shù)拷貝上去.按需定制.
好比開發(fā)商交給我們房子的時候, 只安裝了水電暖氣等基本設(shè)施.電視機洗衣機顯然是自己去買比較好.

屬性拷貝原理就是遍歷第二個對象, 然后分別把鍵和值填入第一個對象中.類似于
Java代碼  收藏代碼
  1. for (var i in obj2){  
  2.     Obj1[i] = obj2[i]  
  3. }  

不幸的是jquery里面的做法復(fù)雜的多,不僅可以把多個參數(shù)的屬性都復(fù)制到一個對象中,還能實現(xiàn)深度繼承.
深度繼承是指如果被繼承對象和繼承對象都有同名屬性a, 而且這個屬性的值分別是一個對象b和c.會把b和c的屬性都合并到一個新的對象里, 作為值返回給合并后的對象.
說的有點復(fù)雜, 看個例子.
Java代碼  收藏代碼
  1.       
  2. jQuery.extend( true,          
  3.          { name: “John”, location: { city: “Boston” } },         
  4.          { last: “Resig”, location: { state: “MA” } }         
  5.      );          
  6.       結(jié)果為         
  7.        {   
  8. name: “John”, last: “Resig”,          
  9.         location: { city: “Boston”, state: “MA” }   
  10.        }  

jQuery的做法主要是這樣, 先修正參數(shù),根據(jù)第一個參數(shù)是boolean還是object確定target是哪個對象.
如果有且只有一個object類型的參數(shù), 則把這個參數(shù)里的屬性copy到j(luò)Query或者jQuery.prototype上, 擴展jQuery對象.用這個辦法可以很方便的編寫插件.
否則依次遍歷第一個object參數(shù)之后的所有object類型對象的屬性. 根據(jù)是否深度繼承用不同的方式進行copy操作.
extend會改變第一個參數(shù)的引用. 如果不想改變它,可以稍微變通一下,把所有的參數(shù)屬性都拷貝到一個空對象上.如$.extend({}, obj1, obj2);
Java代碼  收藏代碼
  1. jQuery.extend = jQuery.fn.extend = function() {  
  2.     var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy, copyIsArray;  
  3.     //target應(yīng)該指向被拷貝的對象, deep表示深度拷貝或者淺度拷貝, 如果為true,即深度拷貝.反之為淺度拷貝.  
  4.   
  5.     if ( typeof target === "boolean" ) {  
  6.     //如果顯示聲明了是深度拷貝還是淺度拷貝  
  7.         deep = target;  
  8.     //重新設(shè)置deep  
  9.         target = arguments[1] || {};  
  10.     //修正target, 讓它指向第二個參數(shù)  
  11.         i = 2;  
  12.     //修正循環(huán)開始的位置, copy的時候跳過deep和target, 指向第一個copy對象.  
  13.     }  
  14.   
  15.     if ( typeof target !== "object" && !jQuery.isFunction(target) ) {  
  16.         //修正target. 確保target是一個可以迭代屬性的對象, 從這句代碼看出target可以為函數(shù)對象  
  17.         target = {};  
  18.     }  
  19.     if ( length === i ) {  
  20.         target = this;  
  21.         --i;  
  22.         /* 
  23.             當(dāng)只有一個參數(shù)是對象的情況(可能前面還有一個true或false).  
  24.             把屬性賦值給jquery或者jquery.fn. 幫jquery實現(xiàn)繼承. 
  25.             并且重新修正循環(huán)起始位置. 
  26.         */  
  27.     }  
  28.   
  29.     for ( ; i < length; i++ ) {  
  30.         if ( (options = arguments[ i ]) != null ) {  
  31.             //只遍歷不為null的參數(shù)  
  32.             for ( name in options ) {  //遍歷copy對象的屬性  
  33.                 src = target[ name ];    //記錄target的屬性值  
  34.                 copy = options[ name ];  //記錄將要被copy進去的值  
  35.                 if ( target === copy ) { //防止死循環(huán)???  
  36.                     continue;  
  37.                 }  
  38.   
  39.                 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {  
  40.                     //如果是深度繼承并且copy是對象字面量或者數(shù)組, 但必須保證至少為對象字面量和數(shù)組之一才會繼續(xù)下面.  
  41.                     //copyIsArray = jQuery.isArray(copy)返回的是boolean.  
  42.                     //如果jQuery.isPlainObject(copy)和jQuery.isArray(copy)都是false,那整個if語句里的判斷條件都返回false,  
  43.                     //也就不會執(zhí)行下面了的深度繼承了.比起1.42版本, 很多地方功能沒變, 代碼還是明顯清晰了很多.  
  44.                     if ( copyIsArray ) {   //如果是數(shù)組  
  45.                         copyIsArray = false//重設(shè)copyIsArray  
  46.                         clone = src && jQuery.isArray(src) ? src : [];  
  47.                     //防止深度copy的時候, target為undefined. 此時target應(yīng)該為對象類型.   
  48.                     //如果target本來為undefined, 要設(shè)一個不含屬性的默認值, 且類型要跟copy對象保持一致.  
  49.                     } else {  
  50.                         clone = src && jQuery.isPlainObject(src) ? src : {};  
  51.                     //同上  
  52.                     }  
  53.                     target[ name ] = jQuery.extend( deep, clone, copy );  
  54.                     //深度繼承遞歸調(diào)用extend方法進行合并  
  55.                 } else if ( copy !== undefined ) {  
  56.                     target[ name ] = copy;  
  57.                     //淺度繼承直接復(fù)制屬性, 但是不要帶入undefined值.  
  58.                 }  
  59.             }  
  60.         }  
  61.     }  
  62.   
  63.     return target;  
  64.     //返回合并后的對象. 第一個obj參數(shù)的引用已經(jīng)被改變了.  
  65. }  














[/size][/size]

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    老司机精品在线你懂的| 亚洲伦片免费偷拍一区| 久久热中文字幕在线视频| 亚洲一区二区三区中文久久 | 久久一区内射污污内射亚洲 | 久久热在线视频免费观看| 黄色国产自拍在线观看| 欧美午夜性刺激在线观看| 免费黄片视频美女一区| 日韩蜜桃一区二区三区| 少妇视频一区二区三区| 国产成人精品国内自产拍| 国产户外勾引精品露出一区 | 国语久精品在视频在线观看| 国产三级视频不卡在线观看| 中文字幕日产乱码一区二区| 欧美日韩精品综合在线| 日韩欧美国产亚洲一区| 亚洲黑人精品一区二区欧美| 欧美国产日产综合精品| 日本一本不卡免费视频| 四十女人口红哪个色好看| 91人人妻人人爽人人狠狠| 日韩欧美一区二区亚洲| 欧美日韩精品综合一区| 男女午夜在线免费观看视频| 人妻一区二区三区在线| 人妻亚洲一区二区三区| 欧美一区二区三区播放| 国产精品内射视频免费| 中文字幕日产乱码一区二区| 欧美在线观看视频免费不卡| 亚洲天堂国产精品久久精品| 日韩成人午夜福利免费视频| 欧美大胆女人的大胆人体| 欧美三级大黄片免费看| 久久老熟女一区二区三区福利| 国产一区二区精品高清免费| 欧美黄色成人真人视频| 麻豆亚州无矿码专区视频| 欧洲偷拍视频中文字幕|