jQuery技術解密二

2.2.6 延續 – 迭代器

在 jQuery 框架中,jQuery 對象是一個很奇怪的概念,具有多重身份,所以很多初學者一聽說 jQuery 對象就感覺很是不解,誤以爲它是 John Resig 製造的新概念。我們可以對jQuery 對象進行如下分解。

第一,jQuery 對象是一個數據集合,它不是一個個體對象。因此,你無法直接使用 JavaScript 的方法來操作它。

第二,jQuery 對象實際上就是一個普通的對象,因爲它是通過 new 運算符創建的一個新的實例對象。它可以繼承原型方法或屬性,同時也擁有 Object 類型的方法和屬性。

第三,jQuery 對象包含數組特性,因爲它賦值了數組元素,以數組結構存儲返回的數據。我們可以以 JavaScript 的概念理解 jQuery 對象,例如下面的示例。

[html] view plaincopy


  1. <script type="text/javascript">  
  2.     var jquery = {          // 定義對象直接量  
  3.         name: "jQuery",     // 以屬性方式存儲信息  
  4.         value: "1.3.2"  
  5.     };  
  6.     jquery[0] = "jQuery";   // 以數組方式存儲信息  
  7.     jquery[1] = "1.3.2";      
  8.     alert(jquery.name);     // 返回 "jQuery"  
  9.     alert(jquery[0]);       // 返回 "jQuery"  
  10. </script>  


上面的 jQuery 對象就是一個典型的 jQuery 對象,jQuery 對象的結構就是按這種形式設計的。可以說,jQuery
對象就是對象和數組的混合體,但是它不擁有數組的方法,因爲它的數組結構是人爲附加的,也就是說它不是 Array 類型數據,而是 Object 類型數據。

第四,jQuery 對象包含的數據都是 DOM 元素,是通過數組形式存儲的,即通過 jQuery[n] 形式獲取。同時 jQuery 對象又定義了幾個模仿 Array 基本特性的屬性,如 length 等。

所以,jQuery 對象是不允許直接操作的,只有分別讀取它包含的每一個 DOM 元素,才能實現各種操作,如插入、刪除、嵌套、賦值和讀寫 DOM 元素屬性等。

那麼如何實現直接操作 jQuery 對象中的 DOM 元素呢?

在實際應用中,我們可以看到類似下面的 jQuery 用法。

$("div").html()

也就是直接在 jQuery 對象上調用 html(),並實現操作 jQuery 包含的所有 DOM 元素。那麼這個功能是怎麼實現的呢?

jQuery 定義了一個工具函數 each(),利用這個工具可以遍歷 jQuery 對象中所有的 DOM 元素,並把需要操作的內容封裝到一個回調函數中,然後通過在每個 DOM 元素上調用這個回調函數即可。實現代碼如下所示,演示效果如圖 2.2 所示。

[html] view plaincopy


  1. <script type="text/javascript">  
  2. var $ = jQuery = function(selector, context){           // 定義類        
  3.     return new jQuery.fn.init(selector, context);       // 返回選擇器的實例  
  4. };  
  5.   
  6. jQuery.fn = jQuery.prototype = {                // jQuery 類的原型對象  
  7.     init: function(selector, context){          // 定義選擇器構造器  
  8.         selector = selector || document;        // 設置默認值爲 document  
  9.         context = context || document;          // 設置默認值爲 document  
  10.           
  11.         if(selector.nodeType){                  // 如果選擇符爲節點對象  
  12.             this[0] = selector;                 // 把參數節點傳遞給實例對象的數組  
  13.             this.length = 1;                    // 並設置實例對象的 length 屬性,定義包含的元素個數  
  14.             this.context = selector;            // 設置實例的屬性,返回選擇範圍  
  15.             return this;                        // 返回當前實例  
  16.         }  
  17.         if(typeof selector === "string"){       // 如果選擇符是字符串  
  18.             var e = context.getElementsByTagName(selector); // 獲取指定名稱的元素  
  19.             for(var i = 0; i<e.length; i++){ // 遍歷元素集合,並把所有元素填入到當前實例數組中  
  20.                 this[i] = e[i];  
  21.             }  
  22.             this.length = e.length;             // 設置實例的 length 屬性,即定義包含的元素個數  
  23.             this.context = context;             // 設置實例的屬性,返回選擇範圍  
  24.             return this;                        // 返回當前實例  
  25.         } else {  
  26.             this.length = 0;                    // 否則,設置實例的 length 屬性值爲 0  
  27.             this.context = context;             // 設置實例的屬性,返回選擇範圍  
  28.             return this;                        // 返回當前實例  
  29.         }  
  30.     },  
  31.     html: function(val){        // 模仿 jQuery 框架中的 html() 方法,爲匹配的每一個DOM元素插入html代碼  
  32.         jQuery.each(this, function(val){    // 調用 jQuery.each() 工具函數,爲每一個 DOM 元素執行回調函數  
  33.             this.innerHTML = val;  
  34.         }, val);  
  35.     },  
  36.     jquery: "1.3.2",                // 原型屬性  
  37.     size: function(){               // 原型方法  
  38.         return this.length;  
  39.     }  
  40. };  
  41.   
  42. jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型對象覆蓋 init 的原型對象  
  43. // 擴展 jQuery 工具函數  
  44. jQuery.each = function(object, callback, args){  
  45.     for(var i=0; i<object.length; i++){  
  46.         callback.call(object[i], args);  
  47.     }  
  48.     return object;  
  49. };  
  50.   
  51. $("div").html("測試代碼");  
  52. </script>  


在上面的示例中,通過先爲自己的 jQuery 對象綁定 html() 方法,然後利用 jQuery() 選擇器獲取頁面中所有的 div 元素,再調用 html() 方法,爲所有匹配的元素插入 HTML 源碼。

注意,在上面的代碼中,each() 函數的當前作用對象是 jQuery 對象,故 this 指向當前 jQuery 對象,即 this 表示一個集合對象;而在 html() 方法中,由於 each() 函數是在指定 DOM 元素上執行的,所以該函數內的 this 指針指向的是當前 DOM 元素對象,即 this 表示一個元素。


2.2.7 延續 – 功能擴展

根據一般設計習慣,如果要爲 jQuery 或者 jQuery.prototype 添加函數或方法,可以直接通過點語法實現,或者在 jQuery.prototype 對象結構中增加一個屬性即可。但是,如果分析 jQuery 框架的源代碼,你會發現它是通過 extend() 函數來實現功能擴展的。例如,下面兩段代碼都是 jQuery 框架通過 extend() 函數來擴展功能的。

jQuery.extend({          // 擴展工具函數

noConflict: function(deep){},

isFunction: function(obj){},

isArray: function(obj){},

isXMLDoc: function(elem){},

globalEval: function(data){}

});

或者

jQuery.fn.extend({  // 擴展 jQuery 對象方法

show: function(speed, callback){},

hide: function(speed, callback){},

toggle: function(fn, fn2){},

fadeTo: function(speed, to, callback){},

animate: function(prop, speed, easing, callback){},

stop: function(clearQueue, gotoEnd){}

});

這樣做的好處是什麼呢?

extend() 函數能夠方便用戶快速擴展 jQuery 框架的功能,但是不會破壞 jQuery 框架的原型結構,從而避免後期人工手動添加工具函數或者方法破壞 jQuery 框架的單純性,同時也方便管理。如果不需要某個插件,只需要簡單地刪除即可,而不需要在 jQuery 框架源代碼中去篩選和刪除。

extend() 函數的功能實現起來也很簡單,它只是把指定對象的方法複製給 jQuery 對象或者 jQuery.prototype 對象。例如,在下面的示例中,我們爲 jQuery 類和原型定義了一個擴展功能的函數 extend() ,該函數的功能很簡單,它能夠把指定參數對象包含的所有屬性複製給 jQuery 或者 jQuery.prototype 對象,這樣就可以在應用中隨時調用它,並動態擴展 jQuery 對象的方法。

[html] view plaincopy


  1. <div></div>  
  2. <div></div>  
  3. <div></div>  
  4. <script type="text/javascript">  
  5. var $ = jQuery = function(selector, context){           // 定義類        
  6.     return new jQuery.fn.init(selector, context);       // 返回選擇器的實例  
  7. };  
  8.   
  9. jQuery.fn = jQuery.prototype = {                // jQuery 類的原型對象  
  10.     init: function(selector, context){          // 定義選擇器構造器  
  11.         selector = selector || document;        // 設置默認值爲 document  
  12.         context = context || document;          // 設置默認值爲 document  
  13.           
  14.         if(selector.nodeType){                  // 如果選擇符爲節點對象  
  15.             this[0] = selector;                 // 把參數節點傳遞給實例對象的數組  
  16.             this.length = 1;                    // 並設置實例對象的 length 屬性,定義包含的元素個數  
  17.             this.context = selector;            // 設置實例的屬性,返回選擇範圍  
  18.             return this;                        // 返回當前實例  
  19.         }  
  20.         if(typeof selector === "string"){       // 如果選擇符是字符串  
  21.             var e = context.getElementsByTagName(selector); // 獲取指定名稱的元素  
  22.             for(var i = 0; i<e.length; i++){ // 遍歷元素集合,並把所有元素填入到當前實例數組中  
  23.                 this[i] = e[i];  
  24.             }  
  25.             this.length = e.length;             // 設置實例的 length 屬性,即定義包含的元素個數  
  26.             this.context = context;             // 設置實例的屬性,返回選擇範圍  
  27.             return this;                        // 返回當前實例  
  28.         } else {  
  29.             this.length = 0;                    // 否則,設置實例的 length 屬性值爲 0  
  30.             this.context = context;             // 設置實例的屬性,返回選擇範圍  
  31.             return this;                        // 返回當前實例  
  32.         }  
  33.     },  
  34.     jquery: "1.3.2",                // 原型屬性  
  35.     size: function(){               // 原型方法  
  36.         return this.length;  
  37.     }  
  38. };  
  39.   
  40. jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型對象覆蓋 init 的原型對象  
  41. // jQuery 功能擴展函數  
  42. jQuery.extend = jQuery.fn.extend = function(obj){  
  43.     for(var prop in obj){  
  44.         this[prop] = obj[prop];  
  45.     }  
  46.     return this;  
  47. };  
  48. // 擴展 jQuery 對象方法  
  49. jQuery.fn.extend({  
  50.     test: function(){  
  51.         alert("測試擴展功能");  
  52.     }  
  53. });  
  54. // 測試代碼  
  55. $("div").test();  
  56. </script>  

在上面的示例中,先定義了一個功能擴展函數 extend(),然後爲 jQuery.fn 原型對象調用 extend() 函數,爲其添加一個測試方法 test()。這樣就可以在實踐中應用,如 $("div").test() 。

jQuery 框架定義的 extend() 函數的功能要強大很多,它不僅能夠完成基本的功能擴展,還可以實現對象合併等功能。

2.2.8 延續 – 參數處理

在很多時候,你會發現 jQuery 的方法都要求傳遞的參數爲對象結構,例如:

$.ajax({

type: "GET",

url: "test.js",

dataType: "script"

});

使用對象直接量作爲參數進行傳遞,方便參數管理。當方法或者函數的參數長度不固定時,使用對象直接量作爲參數存在很多優勢。例如,對於下面的用法,ajax()函數就需要進行更加複雜的參數排查和過濾。

$.ajax("GET", "test.js", "script");

如果 ajax() 函數的參數長度是固定的,且是必須的,那麼通過這種方式進行傳遞也就無所謂了,但是如果參數的個數和排序是動態的,那麼使用 $.ajax("GET", "test.js", "script"); 這種方法是無法處理的。而 jQuery 框架的很多方法都包含大量的參數,且都是可選的,位置也沒有固定要求,所以使用對象直接量是惟一的解決方法。

使用對象直接量作爲參數傳遞的載體,這裏就涉及參數處理問題。如何解析並提出參數?如何處理參數和默認值?我們可以通過下面的方法來實現。

[html] view plaincopy


  1. <script type="text/javascript">  
  2. var $ = jQuery = function(selector, context){           // 定義類        
  3.     return new jQuery.fn.init(selector, context);       // 返回選擇器的實例  
  4. };  
  5.   
  6. jQuery.fn = jQuery.prototype = {                // jQuery 類的原型對象  
  7.     init: function(selector, context){          // 定義選擇器構造器  
  8.         selector = selector || document;        // 設置默認值爲 document  
  9.         context = context || document;          // 設置默認值爲 document  
  10.           
  11.         if(selector.nodeType){                  // 如果選擇符爲節點對象  
  12.             this[0] = selector;                 // 把參數節點傳遞給實例對象的數組  
  13.             this.length = 1;                    // 並設置實例對象的 length 屬性,定義包含的元素個數  
  14.             this.context = selector;            // 設置實例的屬性,返回選擇範圍  
  15.             return this;                        // 返回當前實例  
  16.         }  
  17.         if(typeof selector === "string"){       // 如果選擇符是字符串  
  18.             var e = context.getElementsByTagName(selector); // 獲取指定名稱的元素  
  19.             for(var i = 0; i<e.length; i++){ // 遍歷元素集合,並把所有元素填入到當前實例數組中  
  20.                 this[i] = e[i];  
  21.             }  
  22.             this.length = e.length;             // 設置實例的 length 屬性,即定義包含的元素個數  
  23.             this.context = context;             // 設置實例的屬性,返回選擇範圍  
  24.             return this;                        // 返回當前實例  
  25.         } else {  
  26.             this.length = 0;                    // 否則,設置實例的 length 屬性值爲 0  
  27.             this.context = context;             // 設置實例的屬性,返回選擇範圍  
  28.             return this;                        // 返回當前實例  
  29.         }  
  30.     },  
  31.     setOptions: function(options){  
  32.         this.options = {    // 方法的默認值,可以擴展  
  33.             StartColor: "#000",  
  34.             EndColor: "#DDC",  
  35.             Step: 20,  
  36.             Speed: 10     
  37.         };  
  38.         jQuery.extend(this.options, options || {}); // 如果傳遞參數,則覆蓋原默認參數    
  39.     },  
  40.     jquery: "1.3.2",                // 原型屬性  
  41.     size: function(){               // 原型方法  
  42.         return this.length;  
  43.     }  
  44. };  
  45.   
  46. jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型對象覆蓋 init 的原型對象  
  47.   
  48. jQuery.extend = jQuery.fn.extend = function(destination, source){   // 重新定義 extend() 函數  
  49.     for (var property in source){  
  50.         destination[property] = source[property];  
  51.     }  
  52.     return destination;  
  53. };  
  54. </script>  

在上面的示例中,定義了一個原型方法 setOptions(),該方法能夠對傳遞的參數對象進行處理,並覆蓋默認值。這種用法在本書插件部分還將進行講解。

在 jQuery 框架中, extend() 函數包含了所有功能,它既能夠爲當前對象擴展方法,也能夠處理參數對象,並覆蓋默認值。

2.2.9 涅槃 – 名字空間

現在,我們終於模擬出了 jQuery 框架的雛形,雖然它還比較稚嫩,經不起風雨,但至少能夠保證讀者理解 jQuery 框架構成的初期狀態。不過對於一個成熟的框架來說,需要設計者考慮的問題還是很多的,其中最核心的問題就是名字空間衝突問題。

當一個頁面中存在多個框架,或者自己寫了很多 JavaScript 代碼,我們是很難確保這些代碼不發生衝突的,因爲任何人都無法確保自己非常熟悉 jQuery 框架中的每一行代碼,所以難免會出現名字衝突,或者功能覆蓋現象。爲了解決這個問題,我們必須把 jQuery 封裝在一個孤立的環境中,避免其他代碼的干擾。

在詳細講解名字空間之前,我們先來溫習兩個 JavaScript 概念。首先,請看下面的代碼。

var jQuery = function(){};

jQuery = function(){};

上面所示的代碼是兩種不同的寫法,且都是合法的,但是它們的語義完全不同。第一行代碼聲明瞭一個變量,而第二行代碼定義了 Window 對象的一個屬性,也就是說它等同於下面的語句。

window.jQuery = function();

在全局作用域中,變量和 Window 對象的屬性是可以相等的,也可以是互通的,但是當在其他環境中 (如局部作用域中),則它們是不相等的,也是無法互通的。

因此如果希望 jQuery 具有類似 $.method(); 調用方式的能力,就需要將 jQuery 設置爲 Window 對象的一個屬性,所以你就會看到 jQuery 框架中是這樣定義的。

[html] view plaincopy


  1. <script type="text/javascript">  
  2.     var jQuery = window.jQuery = window.$ = function(selector, context){  
  3.         return new jQuery.fn.init(selector, context);  
  4.     };  
  5. </script>  


你可能看到過下面的函數用法。

(function(){

alert("觀察我什麼時候出現");

})();

這是一個典型的匿名函數基本形式。爲什麼要用到匿名函數呢?

這時就要進入正題了,如果希望自己的 jQuery 框架與其他任何代碼完全隔離開來,也就是說如果你想把 jQuery 裝在一個封閉空間中,不希望暴露內部信息,也不希望別的代碼隨意訪問,匿名函數就是一種最好的封閉方式。此時我們只需要提供接口,就可以方便地與外界進行聯繫。例如,在下面的示例中分別把 f1 函數放在一個匿名函數中,而把 f2 函數放在全局作用域中。可以發現,全局作用域中的 f2 函數可以允許訪問,而匿名函數中的 f1 函數是禁止外界訪問的。

[html] view plaincopy


  1. <script type="text/javascript">  
  2.     (function(){  
  3.         function f1(){  
  4.             return "f1()";  
  5.         }  
  6.     })();  
  7.       
  8.     function f2(){  
  9.         return "f2()";  
  10.     }  
  11.       
  12.     alert(f2());    // 返回 "f2()"  
  13.     alert(f1());    // 拋出異常,禁止訪問  
  14. </script>  


實際上,上面的匿名函數就是所謂的閉包,閉包是 JavaScript 函數中一個最核心的概念。

當然,jQueryjQuery 名字,也許讀者也會定義自己的變量 jQuery 。

在這之前我們需要讓它與其他框架協同工作,這就帶來一個問題,如果我們都使用 作爲簡寫形式就會發生衝突,<strong><span style="color:rgb(255,0,0)">爲此 jQuery 提供了一個 noConflit() 方法,該方法能夠實現禁止 jQuery 框架使用這兩個名字。爲了實現這樣的目的,jQuery 在框架的最前面,先使用 _ 和 _jQuery 臨時變量寄存 和 jQuery 這兩個變量的內容,當需要禁用 jQuery 框架的名字時,可以使用一個臨時變量 _ 和 _jQuery 恢復 $ 和 jQuery 這兩個變量的實際內容。實現代碼如下。

[html] view plaincopy


  1. <script type="text/javascript">  
  2. (function(){      
  3. var   
  4.     window = this,  
  5.     undefined,  
  6.     _jQuery = window.jQuery,    // 暫存 jQuery 變量內容  
  7.     _&nbsp;=&nbsp;window. ,              // 暫存 $ 變量內容  
  8.     jQuery = window.jQuery = window.$ = function(selector, context){  
  9.         return new jQuery.fn.init(selector, context);  
  10.     },  
  11.     quickExpr = /^[^<]*(<(.|\s)+>)[^>]*|^#([\w-]&#43;) /,  
  12.     isSimple = /^.[^:#\[\.,]*$/;  
  13.       
  14. jQuery.fn = jQuery.prototype = {  
  15.     init: function(selector, context){          // 定義選擇器構造器  
  16.         selector = selector || document;        // 設置默認值爲 document  
  17.         context = context || document;          // 設置默認值爲 document  
  18.           
  19.         if(selector.nodeType){                  // 如果選擇符爲節點對象  
  20.             this[0] = selector;                 // 把參數節點傳遞給實例對象的數組  
  21.             this.length = 1;                    // 並設置實例對象的 length 屬性,定義包含的元素個數  
  22.             this.context = selector;            // 設置實例的屬性,返回選擇範圍  
  23.             return this;                        // 返回當前實例  
  24.         }  
  25.         if(typeof selector === "string"){       // 如果選擇符是字符串  
  26.             var e = context.getElementsByTagName(selector); // 獲取指定名稱的元素  
  27.             for(var i = 0; i<e.length; i++){ // 遍歷元素集合,並把所有元素填入到當前實例數組中  
  28.                 this[i] = e[i];  
  29.             }  
  30.             this.length = e.length;             // 設置實例的 length 屬性,即定義包含的元素個數  
  31.             this.context = context;             // 設置實例的屬性,返回選擇範圍  
  32.             return this;                        // 返回當前實例  
  33.         } else {  
  34.             this.length = 0;                    // 否則,設置實例的 length 屬性值爲 0  
  35.             this.context = context;             // 設置實例的屬性,返回選擇範圍  
  36.             return this;                        // 返回當前實例  
  37.         }  
  38.     },  
  39.     setOptions: function(options){  
  40.         this.options = {    // 方法的默認值,可以擴展  
  41.             StartColor: "#000",  
  42.             EndColor: "#DDC",  
  43.             Step: 20,  
  44.             Speed: 10     
  45.         };  
  46.         jQuery.extend(this.options, options || {}); // 如果傳遞參數,則覆蓋原默認參數    
  47.     },  
  48.     jquery: "1.3.2",                // 原型屬性  
  49.     size: function(){               // 原型方法  
  50.         return this.length;  
  51.     }  
  52. };  
  53.   
  54. jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型對象覆蓋 init 的原型對象  
  55.   
  56. jQuery.extend = jQuery.fn.extend = function(destination, source){   // 重新定義 extend() 函數  
  57.     for (var property in source){  
  58.         destination[property] = source[property];  
  59.     }  
  60.     return destination;  
  61. };  
  62. })();  
  63. </script>  


至此,jQuery 框架的設計模式就初見端倪了,後面的工作就是根據應用需要或者功能需要,使用 extend() 函數不斷擴展 jQuery 的工具函數和 jQuery 對象的方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章