close

以下轉錄自:http://msdn.microsoft.com/zh-tw/asp.net/dd722588.aspx



看過眾多好用的jQuery Plugin,應該能體會Plugin的精神--將常用邏輯封裝成一個Method,日後只需多載入一個js,就可以隨時享用。這樣的概念對資深的程式設 計人員來說一點都不陌生,跟元件化、模組化的理念是一致的,而在我們開發專案的過程中,也常有將通用邏輯提取出成為共用元件、函數的情境,在jQuery 網頁設計上,我們可以透過自製Plugin完成。

【基本樣版】

要 自已動手建立一個jQuery Plugin,一點都不困難。首先,我們只需新增一個js檔案,先寫好以下的樣版:

檢視原始文字複製 到剪貼簿print?
  1. (function($) {  
  2.     $.fn.extend({  
  3.         method1: function(options) {  
  4.             //...code...      
  5.         },  
  6.         method2: function(options) {  
  7.             //...code...  
  8.         }  
  9.      });  
  10. })(jQuery);  

這個樣版本用了幾個有趣的技巧,值得好好介紹一下。首先,我們可以在最外圈看到一個(function($) { ... })(jQuery);的寫法,我叫它可拋式匿名函數(補 充說明),主要用意在於避免留下任何全域(Global)性質的函數或變數,因為我們無法掌握js如何被別人引用,會與哪些js合併使用,難保不 會發生跟別的js取到同名同姓的函數或變數。因此,我們把所有的函數及變數宣告都包在一個function() { ... }中,以區域變數性質存在,就不會"撞名"的困擾發生。

但是,如果宣告成區域變數,等匿名函數結束,區域變數不就消失了? 我們如果在匿名函數中對某個元素掛了事件函數,該事件函數中又參考了這個區域變數,豈不就出包了?

答案是,不會! 神奇的Closure特性會避免這種狀況。用一個例子來說明:

檢視原始文字複製 到剪貼簿print?
  1. (function() {  
  2.    var x = 1;  
  3.    $("span").click(function() {  
  4.       alert("In event: " + x);  
  5.    });  
  6. })();  
  7. alert("Out of scope: " + typeof x);  

在這個有趣的例子裡,會看到Out of scope: undefined的結果,但按下span,卻會看到In event: 1。換句話說,Javascript在宣告$("span").click(function() { ... });時,一併記錄了在範圍之外的x變數,即便x是區域變數,在匿名函數結束時就該消失,但因被其中額外宣告的匿名函數引用,便被保存下來。 Closure挺奧妙的,有興趣的人可以再看看這篇補 充說明。

回到正題,由於jQuery允許透過jQuery.noConflict()函數將$符號留給其他Javascript Framework使用,因此我們無法確定Plugin被呼叫時,是否$仍等同於jQuery(),一種解決方法是不要用$,一律乖乖寫jQuery;而 另外一個做法,就如同上面程式示範,匿名函數宣告接入一個參數$,而在呼叫匿名函數時,順便傳入jQuery當作參數。換句話說,在此匿名函數的範圍 中,$就一定等同於外部傳入的jQuery,不必再擔心是否啟用了jQuery.noConflict()。

解釋完外圈匿名函數的用意,我 們來看看如何新增像addClass()、removeClass()一樣可以用在jQuery物件上的Method。我們可以直接宣告如: $.fn.myMethod = function() { alert($(this).html()); };。 接著jQuery物件就多了myMethod()可用,如: $("span").myMethod(); ,這是最簡單的方法。但實務上,我們會用extend()達成同樣目標: $.fn.extend({ myMethod: function() { alert($(this).html()) } });。extend()也常用在參 數處理上,稍後再詳細介紹。

【小試身手】

了解基本樣版 後,讓我們來搞一個小範例,寫兩個自訂函數,一個讓元素上下跳(jump()),另一個讓元素左右搖(shake()),此處用先前介紹過的動畫技巧來達 成。有個小撇步,我們要做出跳動及搖動的並不是目標元素,而是另外建出position=absolute的分身,而原有元素的visibility則設 為hidden(不用display=none是因為要保留其佔用空間,不然會打亂原有配置),待分身表演退場後再現身。分身元素的position設成 absolute,top及left則由目標元素的所在位置取得,接著就可以用animate()去調marginTop,先0.1秒向上 10px,0.1秒向下20px,再0.1秒向上10px回到原位。做完效果後,分身移除,本尊還原,一切恢復正常。程式會像以下的樣子:

【小試身手】

了解基本樣版後,讓我們來搞一個小範例,寫兩個自訂函數,一個讓元素上下跳(jump()),另一個讓元素左右搖(shake()),此處用先前介紹過的動畫技巧來達成。有個小撇步,我們要做出跳動及搖動的並不是目標元素,而是另外建出position=absolute的分身,而原有元素的visibility則設為hidden(不用display=none是因為要保留其佔用空間,不然會打亂原有配置),待分身表演退場後再現身。分身元素的position設成absolute,top及left則由目標元素的所在位置取得,接著就可以用animate()去調marginTop,先0.1秒向上10px,0.1秒向下20px,再0.1秒向上10px回到原位。做完效果後,分身移除,本尊還原,一切恢復正常。程式會像以下的樣子:

檢視原始文字
檢視原始文字複製 到剪貼簿print?
  1. (function($) {  
  2.     $.fn.extend({  
  3.         jump: function() {  
  4.  &nicrosoft.com/zh-tw/asp.net/dd722588.aspx#" onclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">複製到剪貼簿print?
  1. (function($) {  
  2.     $.fn.extend({  
  3.         jump: function() {  
  4.             var target = this;  
  5.             //建立分身  
  6.             var shadow = target.clone().appendTo("body");  
  7.             var origVis = target.css("visibility");  
  8.             //隱藏本尊, 但要保留其佔用空間  
  9.             target.css("visibility""hidden");  
  10.             shadow //設定為絕對座標,以便任意移動  
  11.  &bsp;          var target = this;  
  12.             //建立分身  
  13.             var shadow = target.clone().appendTo("body");  
  14.             var origVis = target.css("visibility");  
  15.             //隱藏本尊, 但要保留其佔用空間  
  16.             target.css(
  17.                     position: "absolute",  
  18.                     top: target.offset().top,  
  19.                     left: target.offset().left,  
  20.                     margin: "0px"  
  21.                 }) //進行三次位移ss="string">"visibility""hidden");  
  22.             shadow //設定為絕對座標,以便任意移動  
  23.                 .css({  
  24.                     position: "absolute",  
  25.                     top: target.offset().top,  
  26.                     left: target.offset().left,  
  27.        &  
  28.                 .animate({ marginTop: "-=10px" }, 100)  
  29.                 .animate({ marginTop: "+=20px" }, 100)  
  30.                 .animate({ marginTop: "-=10px" }, 100, function() {  
  31.                     //本尊重現  
  32.                 &nbnbsp;            margin: "0px"  
  33.                 }) //進行三次位移  
  34.                 .animate({ marginTop: "-=10px" }, 100)  
  35.                 .animate({ marginTop: "+=20px" }, 100)  
  36.                 .animate({ marginTop: "-=10px" }, 100, function() {  
  37.                     //本尊重現  
  38.                     target.css("visibility", origVis);  
  39.                     //狡兔死走狗烹,分身移除  
  40.                     $(this).remove();  
  41.                 });  
  42.   
  43.         }, sp;   target.css("visibility", origVis);  
  44.                     //狡兔死走狗烹,分身移除  
  45.                     $(this).remove();  
  46.                 });  
  47.   
  48.         },  
  49.         shake: function() {  
  50.             var target = this;  
  51.             var shadow = target.clone().appendTo("body");  
  52.             var origVis = target.css("visibility");  
  53.             target.css("visibility""hidden");  
  54.             shadow   
  55.                 .css({  
  56.                  
  57.         shake: function() {  
  58.             var target = this;  
  59.             var shadow = target.clone().appendTo("body");  
  60.             var origVis = target.css("visibility");  
  61.             target.css("visibility""hidden");  
  62.             shadow   
  63.                 .css({  
  64.                     position: "absolute",  
  65.                     top: target.offset().top,  
  66.                     left: target.offset().left,  
  67.                     margin: "0px"  
  68.    &nb;    position: "absolute",  
  69.                     top: target.offset().top,  
  70.                     left: target.offset().left,  
  71.                     margin: "0px"  
  72.                 })  
  73.                 .animate({ marginLeft: "-=10px" }, 100)  
  74.  &nbssp;            })  
  75.                 .animate({ marginLeft: "-=10px" }, 100)  
  76.                 .animate({ marginLeft: "+=20px" }, 100)  
  77.                 .animate({ marginLeft: "-=10px" }, 100, function() {  
  78.                     target.css("visibility", origVis);  
  79. p;              .animate({ marginLeft: "+=20px" }, 100)  
  80.                 .animate({ marginLeft: "-=10px" }, 100, function() {  
  81.                     target.css("visibility", origVis);  
  82.                     $(this).remove();  
  83.                 });          
  84.         }  
  85.     });  
  86. })(jQuery);  

前端程式我們放入兩個DIV測試效果:

檢視原始文字複製到剪貼簿print

前端程式我們放入兩個DIV測試效果:

檢視原始文字複製 到剪貼簿print?
  1. <html >  
  2. <head>  
  3.     <title></title>  
  4.     <script src="https://blog.xuite.net/coke750101/networkprogramming/"jquery-1.3.2.js" type="text/javascript"></script>  
  5.     <script src="https://blog.xuite.net/coke750101/networkprogramming/?
  1. <html >  
  2. <head>  
  3.     <title></title>  
  4.     <script src="https://blog.xuite.net/coke750101/networkprogramming/"jquery-1.3.2.js" type="text/javascript"></script>  
  5.     <script src="https://blog.xuite.net/coke750101/networkprogramming/"jquery.myPlugin.1.js" type="text/javascript"></script>  
  6.     <style type="text/css">  
  7.     div { width: 50px; height: 50px; margin: 20px; }  
  8.     </style>  
  9. </head>  
  10. <body>  
  11. <div id="square1" style="background-color: Blue;">  
  12. </div>  
  13. <div id="square2" style="background-color: Red;">  
  14. </div>  
  15. <script type="text/javascript">  
  16.     $(function() {  
  17.         $("#square1").jump();  
  18.         $("#square2").shake();  
  19.     });  
  20. </script>  
  21. </body> ss="string">"jquery.myPlugin.1.js" type="text/javascript"></script>  
  22.     <style type="text/css">  
  23.     div { width: 50px; height: 50px; margin: 20px; }  
  24.     </style>  
  25. </head>  
  26. <body>  
  27. <div id="square1" style="background-color: Blue;">  
  28. </div>  
  29. <div id="square2" style="background-color: Red;">  
  30. </div>  
  31. <script type="text/javascri 
  32. </html>  

程式是可行了,但jump()與shake()的程式碼只差在animate時移動的CSS屬性,重覆性極高,程式碼重複冗長不夠簡潔,未來若要維護修改得一次改兩個地方,故我們進行簡單的重構,提取出共用函數。

>  
  •     $(function() {  
  •         $("#square1").jump();  
  •         $("#square2").shake();  
  •     });  
  • </script>  
  • </body>  
  • </html>  
  • 【錦上添花】

    基本功能有了,但目前我們的 Plugin只提供了固定的移動值,如果開發者想要跳高一點或搖大力一點怎麼辦? 為了提供給Plugin使用者更大的彈性,我們決定仿效其他的專業Plugin,允許在呼叫時傳入額外參數,以控制動畫行為。n style="font-size: 12pt;">jQuery.extend()很常用來處理Plugin或函數的傳入參數,比如函數用到的參數有10個,但大部分情況呼叫時只需要指定其中一兩個,其餘用預設值。我們便可在函數中宣告一個預設值物件objDefault,裡面先放上10個屬性當預設值,,呼叫函數時則傳入objOption,裡面只放要特別指定的屬性值,經過var objSetting = jQuery.extend(objDefault, objOption)之後,我們得到10個"有指定用指定值,沒指定用預設值"的屬性群組供後續使用。舉個例子:

    檢視原始文字複製到剪貼簿

    一般來說,我 們會用一個匿名物件當成參數的載具,Plugin在接收後,會逐一取出物件上的各屬性取得參數值。但有個小問題,如果可以操控的參數有20個,開發者可能 只想修改其中3個,其餘17個則沿用預設值,此時匿名物件上應該只要設定三個指定的屬性即可,沒必要20個都註明。這種有給值就用指定值,沒給就用預設值 的設計架構,jQuery提供了一個好用的函數extend()輕鬆達成目標。

    jQuery.extend()的運作結果有點抽象,故直接 以實例說明。以jQuery.extend(objA, objB)為例,你可以想像成objA與objB各有一些屬性(方法也會比照處理,在此只提屬性),extend()會將objB有而objA沒有的屬性 加到objA裡,如果objB裡的某個屬性,objA裡剛好也有同名屬性,則會用objB的屬性值覆寫。objA最後就是整合結果,或者也可以由var objC = jQuery.extend(objA, objB)取得整合結果。(objA與objC內容相同)

    jQuery.extend() 可以支援多個物件屬性/方法的整併,並不限兩個。例如: jQuery.extend(objA, objB, objC),objB, objC多出的屬性都會加到objA裡,如果有objA已有同名屬性,則會用objC/objB裡的屬性值覆寫之,若objB, objC都有同名屬性,則會排在後方的objC為準(後令壓前令)。

    jQuery.extend()很常用來處理Plugin或函數的傳入 參數,比如函數用到的參數有10個,但大部分情況呼叫時只需要指定其中一兩個,其餘用預設值。我們便可在函數中宣告一個預設值物件objDefault, 裡面先放上10個屬性當預設值,,呼叫函數時則傳入objOption,裡面只放要特別指定的屬性值,經過var objSetting = j/msdn.microsoft.com/zh-tw/asp.net/dd722588.aspx#" onclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print?

    1. function addDiv(options) {  
    2.     var defaults = {   
    3.         border: "solid 1px black",  
    4.         backgroundColor: "#cccccc",  
    5.         width: "200px", height: "50px",  
    6.      &Query.extend(objDefault, objOption)之後,我們得到10個"有指定用指定值,沒指定用預設值"的屬性群組供後續使用。舉個例子:

      檢視原始文字複製 到剪貼簿print?
      1. function addDiv(options) {  
      2.     var defaults = {   
      3.         border: "solid 1px black",  
      4.         backgroundColor: "#cccccc",  
      5.         width: "200px", height: "50px",  
      6.         margin: "10px"  
      7.     };  
      8.  &nbsnbsp;  margin: "10px"  
      9.     };  
      10.     var settings = $.extend(defaults, options);  
      11.     $("<div></div>").css(settings).appendTo("body");  
      12. }  
      13. addDiv({ width: "400px" });  
      14. addDiv({ backgroundColor: "orange", height: "100px" });  
    7.     $("<div></div>").css(settings).appendTo("body");  
    8. }  
    9. addDiv({ width: "400px" });  
    10. addDiv({ backgroundColor: "orange", height: "100px" });  

    了解jQuery.extend()後,我們為jump()及shake()多增加一個參數,可以控制移動的距離以及動畫時間長度,另外再支援一個callback函數,在動作完成後進行呼叫。於是,程式可以改寫如下:

    檢視原始文字複製到剪貼簿print?
    1. (function($) {  
    2.     function moveElem(target, moveAttr, options) {  
    3.         //處理參數  
    4.         var settings = { duration: 100, movement: 10, oncomplete: null };  

    了解jQuery.extend()後,我們為jump()及shake()多增加一個參數,可以控制移動的距離以及動畫時間長度, 另外再支援一個callback函數,在動作完成後進行呼叫。於是,程式可以改寫如下:

    檢視原始文字複製 到剪貼簿print?
    1. (function($) {  
    2.     function moveElem(target, moveAttr, options) {  
    3.         //處理參數  
    4.         var settings = { duration: 100, movement: 10, oncomplete: null };  
    5.         $.exli>
    6.         $.extend(settings, options);  
    7.         //建立分身  
    8.         var shadow = target.clone().appendTo("body");  
    9.         var origVis = target.css("visibility");  
    10.         //將原元素藏起來  
    11.         target.css("visibility""hidden");  
    12.         //建立位移的animiate()參數  
    13.         var opt1 = {}, opt2 = {};  
    14.         opt1[moveAttr] = "-=" + settings.movement + "px";  
    15.         opt2[moveAttr] = "+=" + (settings.movement * 2) + "px";  
    16.         shadow  //設定為絕對座標,以便任意移動  
    17.             .css({  
    18.                 position: "absoluttend(settings, options);  
    19.         //建立分身  
    20.         var shadow = target.clone().appendTo("body");  
    21.         var origVis = target.css("visibility");  
    22.         //將原元素藏起來  
    23.         target.css("visibility""hidden");  
    24.         //建立位移的animiate()參數  
    25.         var opt1 = {}, opt2 = {};  
    26.         opt1[moveAttr] = "-=" + settings.movement + "px";  
    27.         opt2[moveAttr] = "+=" + (settings.movement * 2) + "px";  
    28.         shadow  //設定為絕對座標,以便任意移動  
    29.             .css({  
    30.                 position: "absolute",  
    31.   &ne",  
    32.                 top: target.offset().top,  
    33.                 left: target.offset().left,  
    34.                 margin: "0px"  
    35.             }) //進行三次位移  
    36.             .animate(opt1, settings.duration)  
    37.             .animate(opt2, settings.duration)  
    38.             .absp;             top: target.offset().top,  
    39.                 left: target.offset().left,  
    40.                 margin: "0px"  
    41.             }) //進行三次位移  
    42.             .animate(opt1, settings.duration)  
    43.             .animate(opt2, settings.duration)  
    44.             .animate(opt1, settings.duratinimate(opt1, settings.duration, function() {  
    45.                 //本尊重現  
    46.                 target.css("visibility", origVis);  
    47.                 //分身移除  
    48.                 $(this).remove();  
    49.                 //如有callback,呼叫之  
    50.        on, function() {  
    51.                 //本尊重現  
    52.                 target.css("visibility", origVis);  
    53.                 //分身移除  
    54.                 $(this).remove();  
    55.                 //如有callback,呼叫 之  
    56.           &         if ($.isFunction(settings.oncomplete))  
    57.                     settings.oncomplete();  
    58.             });  
    59.     }  
    60.   
    61.     $.fn.extend({  
    62.         jump: function(options) {  
    63.             moveElem($(this), "marginTop", options);  
    64.         },  
    65.         shake: function(options) {  
    66.             moveElem($(this), "marginLeft", options);  
    67.         }  
    68.     });  
    69. })(jQuery);  

    試試$("#square1").jump({ movement: 30, duration: 500, oncomplete: function() { alert("DONE!"); } });,可驗證移動距離變大,速度變慢,結束時也多彈出DONE字樣。

    【代代相傳】

    到此,我們的Plugin已算功能完整,但有一個小缺憾。jQuery大部分的函數都可以串接,但目前的寫法在呼叫jump()/shake()後,後面若再串上一般的jQuery Method,則會發生該Method找不到的狀況。原因是我們的函數在執行完成後,並未將原本的jQuery物件一五一十地傳承下去。做法很簡單,將原本的邏輯包在return this.each(function() { ... })中即可:

    檢視原始文字複製到剪貼簿print?
    1. $.fn.extend({  
    2.         jump: function(options) {  
    3.             return this.each(function() {  
    4. <); }, shake: function(options) { moveElem($(this), "marginLeft", options); } }); })(jQuery);

    試試$("#square1").jump({ movement: 30, duration: 500, oncomplete: function() { alert("DONE!"); } });,可驗證移動距離變大,速度變慢,結束時也多彈出DONE字樣。

    【代代相傳】

    到 此,我們的Plugin已算功能完整,但有一個小缺憾。jQuery大部分的函數都可以串接,但目前的寫法在呼叫jump()/shake()後,後面若 再串上一般的jQuery Method,則會發生該Method找不到的狀況。原因是我們的函數在執行完成後,並未將原本的jQuery物件一五一十地傳承下去。做法很簡單,將原 本的邏輯包在return this.each(function() { ... })中即可:

    檢視原始文字複製 到剪貼簿print?
    1. $.fn.extend({  
    2.         jump: function(options) {  
    3.           &nbli>                moveElem($(this), "marginTop", options);  
    4.             });  
    5.         },  
    6.         shake: function(options) {  
    7.             return this.each(function() {  
    8.                 moveElem($(this), "marginLeft", options);  
    9.             });  
    10.         }  
    11.     });  

    唯一要留意的地方是,在this.each(function() { ... });中,this指向的會是jQuery元素陣列中的個別元素,故要包成$(this)才能轉化為jQuery物件。我們寫一行: $("#square2").click(function() { $(this).jump().after("<span>SPAN</span>"); }); 就可以在點選時,除了跳動外,還在方塊後方新增<SPAN>元素。return this.each(function() {  

  •                 moveElem($(this), "marginTop", options);  
  •             });  
  •         },  
  •         shake: function(options) {  
  •             return this.each(function() {  
  •          >

    【範例檔案下載】

    • jQueryDemo13.zip
    p;       moveElem($(this), "marginLeft", options);  
  •             });  
  •         }  
  •     });  
  • 唯一要留意的地方是,在this.each(function() { ... });中,this指向的會是jQuery元素陣列中的個別元素,故要包成$(this)才能轉化為jQuery物件。我們寫一行: $("#square2").click(function() { $(this).jump().after("<span>SPAN</span>"); }); 就可以在點選時,除了跳動外,還在方塊後方新增<SPAN>元素。

    【範例 檔案下載】

    • jQueryDemo13.zip