第二章 jQuery技術解密 (二)

mybwu_com發表於2013-12-28

2.2.6 延續 -- 迭代器

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

第一,jQuery 物件是一個資料集合,它不是一個個體物件。因此,你無法直接使用 JavaScript 的方法來操作它。

第二,jQuery 物件實際上就是一個普通的物件,因為它是通過 new 運算子建立的一個新的例項物件。它可以繼承原型方法或屬性,同時也擁有 Object 型別的方法和屬性。

第三,jQuery 物件包含陣列特性,因為它賦值了陣列元素,以陣列結構儲存返回的資料。我們可以以 JavaScript 的概念理解 jQuery 物件,例如下面的示例。

  1. <scripttype="text/javascript">
  2. varjquery={//定義物件直接量
  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 所示。

  1. <scripttype="text/javascript">
  2. var$=jQuery=function(selector,context){//定義類
  3. returnnewjQuery.fn.init(selector,context);//返回選擇器的例項
  4. };
  5. jQuery.fn=jQuery.prototype={//jQuery類的原型物件
  6. init:function(selector,context){//定義選擇器構造器
  7. selector=selector||document;//設定預設值為document
  8. context=context||document;//設定預設值為document
  9. if(selector.nodeType){//如果選擇符為節點物件
  10. this[0]=selector;//把引數節點傳遞給例項物件的陣列
  11. this.length=1;//並設定例項物件的length屬性,定義包含的元素個數
  12. this.context=selector;//設定例項的屬性,返回選擇範圍
  13. returnthis;//返回當前例項
  14. }
  15. if(typeofselector==="string"){//如果選擇符是字串
  16. vare=context.getElementsByTagName(selector);//獲取指定名稱的元素
  17. for(vari=0;i<e.length;i++){//遍歷元素集合,並把所有元素填入到當前例項陣列中
  18. this[i]=e[i];
  19. }
  20. this.length=e.length;//設定例項的length屬性,即定義包含的元素個數
  21. this.context=context;//設定例項的屬性,返回選擇範圍
  22. returnthis;//返回當前例項
  23. }else{
  24. this.length=0;//否則,設定例項的length屬性值為0
  25. this.context=context;//設定例項的屬性,返回選擇範圍
  26. returnthis;//返回當前例項
  27. }
  28. },
  29. html:function(val){//模仿jQuery框架中的html()方法,為匹配的每一個DOM元素插入html程式碼
  30. jQuery.each(this,function(val){//呼叫jQuery.each()工具函式,為每一個DOM元素執行回撥函式
  31. this.innerHTML=val;
  32. },val);
  33. },
  34. jquery:"1.3.2",//原型屬性
  35. size:function(){//原型方法
  36. returnthis.length;
  37. }
  38. };
  39. jQuery.fn.init.prototype=jQuery.fn;//使用jQuery的原型物件覆蓋init的原型物件
  40. //擴充套件jQuery工具函式
  41. jQuery.each=function(object,callback,args){
  42. for(vari=0;i<object.length;i++){
  43. callback.call(object[i],args);
  44. }
  45. returnobject;
  46. };
  47. $("div").html("測試程式碼");
  48. </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 物件的方法。

  1. <div></div>
  2. <div></div>
  3. <div></div>
  4. <scripttype="text/javascript">
  5. var$=jQuery=function(selector,context){//定義類
  6. returnnewjQuery.fn.init(selector,context);//返回選擇器的例項
  7. };
  8. jQuery.fn=jQuery.prototype={//jQuery類的原型物件
  9. init:function(selector,context){//定義選擇器構造器
  10. selector=selector||document;//設定預設值為document
  11. context=context||document;//設定預設值為document
  12. if(selector.nodeType){//如果選擇符為節點物件
  13. this[0]=selector;//把引數節點傳遞給例項物件的陣列
  14. this.length=1;//並設定例項物件的length屬性,定義包含的元素個數
  15. this.context=selector;//設定例項的屬性,返回選擇範圍
  16. returnthis;//返回當前例項
  17. }
  18. if(typeofselector==="string"){//如果選擇符是字串
  19. vare=context.getElementsByTagName(selector);//獲取指定名稱的元素
  20. for(vari=0;i<e.length;i++){//遍歷元素集合,並把所有元素填入到當前例項陣列中
  21. this[i]=e[i];
  22. }
  23. this.length=e.length;//設定例項的length屬性,即定義包含的元素個數
  24. this.context=context;//設定例項的屬性,返回選擇範圍
  25. returnthis;//返回當前例項
  26. }else{
  27. this.length=0;//否則,設定例項的length屬性值為0
  28. this.context=context;//設定例項的屬性,返回選擇範圍
  29. returnthis;//返回當前例項
  30. }
  31. },
  32. jquery:"1.3.2",//原型屬性
  33. size:function(){//原型方法
  34. returnthis.length;
  35. }
  36. };
  37. jQuery.fn.init.prototype=jQuery.fn;//使用jQuery的原型物件覆蓋init的原型物件
  38. //jQuery功能擴充套件函式
  39. jQuery.extend=jQuery.fn.extend=function(obj){
  40. for(varpropinobj){
  41. this[prop]=obj[prop];
  42. }
  43. returnthis;
  44. };
  45. //擴充套件jQuery物件方法
  46. jQuery.fn.extend({
  47. test:function(){
  48. alert("測試擴充套件功能");
  49. }
  50. });
  51. //測試程式碼
  52. $("div").test();
  53. </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 框架的很多方法都包含大量的引數,且都是可選的,位置也沒有固定要求,所以使用物件直接量是惟一的解決方法。

使用物件直接量作為引數傳遞的載體,這裡就涉及引數處理問題。如何解析並提出引數?如何處理引數和預設值?我們可以通過下面的方法來實現。

  1. <scripttype="text/javascript">
  2. var$=jQuery=function(selector,context){//定義類
  3. returnnewjQuery.fn.init(selector,context);//返回選擇器的例項
  4. };
  5. jQuery.fn=jQuery.prototype={//jQuery類的原型物件
  6. init:function(selector,context){//定義選擇器構造器
  7. selector=selector||document;//設定預設值為document
  8. context=context||document;//設定預設值為document
  9. if(selector.nodeType){//如果選擇符為節點物件
  10. this[0]=selector;//把引數節點傳遞給例項物件的陣列
  11. this.length=1;//並設定例項物件的length屬性,定義包含的元素個數
  12. this.context=selector;//設定例項的屬性,返回選擇範圍
  13. returnthis;//返回當前例項
  14. }
  15. if(typeofselector==="string"){//如果選擇符是字串
  16. vare=context.getElementsByTagName(selector);//獲取指定名稱的元素
  17. for(vari=0;i<e.length;i++){//遍歷元素集合,並把所有元素填入到當前例項陣列中
  18. this[i]=e[i];
  19. }
  20. this.length=e.length;//設定例項的length屬性,即定義包含的元素個數
  21. this.context=context;//設定例項的屬性,返回選擇範圍
  22. returnthis;//返回當前例項
  23. }else{
  24. this.length=0;//否則,設定例項的length屬性值為0
  25. this.context=context;//設定例項的屬性,返回選擇範圍
  26. returnthis;//返回當前例項
  27. }
  28. },
  29. setOptions:function(options){
  30. this.options={//方法的預設值,可以擴充套件
  31. StartColor:"#000",
  32. EndColor:"#DDC",
  33. Step:20,
  34. Speed:10
  35. };
  36. jQuery.extend(this.options,options||{});//如果傳遞引數,則覆蓋原預設引數
  37. },
  38. jquery:"1.3.2",//原型屬性
  39. size:function(){//原型方法
  40. returnthis.length;
  41. }
  42. };
  43. jQuery.fn.init.prototype=jQuery.fn;//使用jQuery的原型物件覆蓋init的原型物件
  44. jQuery.extend=jQuery.fn.extend=function(destination,source){//重新定義extend()函式
  45. for(varpropertyinsource){
  46. destination[property]=source[property];
  47. }
  48. returndestination;
  49. };
  50. </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 框架中是這樣定義的。

  1. <scripttype="text/javascript">
  2. varjQuery=window.jQuery=window.$=function(selector,context){
  3. returnnewjQuery.fn.init(selector,context);
  4. };
  5. </script>
你可能看到過下面的函式用法。

(function(){

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

})();

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

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

  1. <scripttype="text/javascript">
  2. (function(){
  3. functionf1(){
  4. return"f1()";
  5. }
  6. })();
  7. functionf2(){
  8. return"f2()";
  9. }
  10. alert(f2());//返回"f2()"
  11. alert(f1());//丟擲異常,禁止訪問
  12. </script>
實際上,上面的匿名函式就是所謂的閉包,閉包是 JavaScript 函式中一個最核心的概念。

當然,$ 和 jQuery 名字並非是 jQuery 框架的專利,其他一些經典框架中也會用到 $ 名字,也許讀者也會定義自己的變數 jQuery 。

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

  1. <scripttype="text/javascript">
  2. (function(){
  3. var
  4. window=this,
  5. undefined,
  6. _jQuery=window.jQuery,//暫存jQuery變數內容
  7. _$=window.$,//暫存$變數內容
  8. jQuery=window.jQuery=window.$=function(selector,context){
  9. returnnewjQuery.fn.init(selector,context);
  10. },
  11. quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
  12. isSimple=/^.[^:#\[\.,]*$/;
  13. jQuery.fn=jQuery.prototype={
  14. init:function(selector,context){//定義選擇器構造器
  15. selector=selector||document;//設定預設值為document
  16. context=context||document;//設定預設值為document
  17. if(selector.nodeType){//如果選擇符為節點物件
  18. this[0]=selector;//把引數節點傳遞給例項物件的陣列
  19. this.length=1;//並設定例項物件的length屬性,定義包含的元素個數
  20. this.context=selector;//設定例項的屬性,返回選擇範圍
  21. returnthis;//返回當前例項
  22. }
  23. if(typeofselector==="string"){//如果選擇符是字串
  24. vare=context.getElementsByTagName(selector);//獲取指定名稱的元素
  25. for(vari=0;i<e.length;i++){//遍歷元素集合,並把所有元素填入到當前例項陣列中
  26. this[i]=e[i];
  27. }
  28. this.length=e.length;//設定例項的length屬性,即定義包含的元素個數
  29. this.context=context;//設定例項的屬性,返回選擇範圍
  30. returnthis;//返回當前例項
  31. }else{
  32. this.length=0;//否則,設定例項的length屬性值為0
  33. this.context=context;//設定例項的屬性,返回選擇範圍
  34. returnthis;//返回當前例項
  35. }
  36. },
  37. setOptions:function(options){
  38. this.options={//方法的預設值,可以擴充套件
  39. StartColor:"#000",
  40. EndColor:"#DDC",
  41. Step:20,
  42. Speed:10
  43. };
  44. jQuery.extend(this.options,options||{});//如果傳遞引數,則覆蓋原預設引數
  45. },
  46. jquery:"1.3.2",//原型屬性
  47. size:function(){//原型方法
  48. returnthis.length;
  49. }
  50. };
  51. jQuery.fn.init.prototype=jQuery.fn;//使用jQuery的原型物件覆蓋init的原型物件
  52. jQuery.extend=jQuery.fn.extend=function(destination,source){//重新定義extend()函式
  53. for(varpropertyinsource){
  54. destination[property]=source[property];
  55. }
  56. returndestination;
  57. };
  58. })();
  59. </script>
至此,jQuery 框架的設計模式就初見端倪了,後面的工作就是根據應用需要或者功能需要,使用 extend() 函式不斷擴充套件 jQuery 的工具函式和 jQuery 物件的方法。

相關文章