若是你看到這篇文章,我確信你毫無疑問會認爲jQuery是一個使用簡便的庫。jQuery可能使用起來很簡單,可是它仍然有一些奇怪的地方,對它基本功能和概念不熟悉的人可能會難以掌握。可是不用擔憂,我下面已經把代碼劃分紅小部分,作了一個簡單的指導。那些語法看起來可能過於複雜,可是若是進入到它的思想和模式中,它是很是簡單易懂的。html
下面,咱們有了一個插件的基本層次:app
// Shawn Khameneh // ExtraordinaryThoughts.com (function($) { var privateFunction = function() { // 代碼在這裏運行 } var methods = { init: function(options) { return this.each(function() { var $this = $(this); var settings = $this.data('pluginName'); if(typeof(settings) == 'undefined') { var defaults = { propertyName: 'value', onSomeEvent: function() {} } settings = $.extend({}, defaults, options); $this.data('pluginName', settings); } else { settings = $.extend({}, settings, options); } // 代碼在這裏運行 }); }, destroy: function(options) { return $(this).each(function() { var $this = $(this); $this.removeData('pluginName'); }); }, val: function(options) { var someValue = this.eq(0).html(); return someValue; } }; $.fn.pluginName = function() { var method = arguments[0]; if(methods[method]) { method = methods[method]; arguments = Array.prototype.slice.call(arguments, 1); } else if( typeof(method) == 'object' || !method ) { method = methods.init; } else { $.error( 'Method ' + method + ' does not exist on jQuery.pluginName' ); return this; } return method.apply(this, arguments); } })(jQuery);
你可能會注意到,我所提到代碼的結構和其餘插件代碼有很大的不一樣。根據你的使用和需求的不一樣,插件的開發方式也可能會呈現多樣化。個人目的是澄清代碼中的一些概念,足夠讓你找到適合本身的方法去理解和開發一個jQuery插件。函數
如今,來解剖咱們的代碼吧! this
1. 容器:一個即時執行函數spa
根本上來講,每一個插件的代碼是被包含在一個即時執行的函數當中,以下:prototype
(function(arg1, arg2) { // 代碼 })(arg1, arg2);
即時執行函數,顧名思義,是一個函數。讓它不同凡響的是,它被包含在一對小括號裏面,這讓全部的代碼都在匿名函數的局部做用域中運行。這並非說DOM(全局變量)在函數內是被屏蔽的,而是外部沒法訪問到函數內部的公共變量和對象命名空間。這是一個很好的開始,這樣你聲明你的變量和對象的時候,就不用擔憂着變量名和已經存在的代碼有衝突。插件
如今,由於函數內部全部的全部公共變量是沒法訪問的,這樣要把jQuery自己做爲一個內部的公共變量來使用就會成爲問題。就像普通的函數同樣,即時函數也根據引用傳入對象參數。咱們能夠將jQuery對象傳入函數,以下:code
(function($) { // 局部做用域中使用$來引用jQuery })(jQuery);
咱們傳入了一個把公共變量「jQuery」傳入了一個即時執行的函數裏面,在函數局部(容器)中咱們能夠經過「$」來引用它。也就是說,咱們把容器當作一個函數來調用,而這個函數的參數就是jQuery。由於咱們引用的「jQuery」做爲公共變量傳入,而不是它的簡寫「$」,這樣咱們就能夠兼容Prototype庫。若是你不用Prototype或者其它用「$」作簡寫的庫的話,你不這樣作也不會形成什麼影響,可是知道這種用法還是一件好事。orm
2. 插件:一個函數htm
一個jQuery插件本質上是咱們塞進jQuery命名空間中一個龐大的函數,固然,咱們能夠很輕易地用「jQuery.pluginName=function」,來達到咱們的目的,可是若是咱們這樣作的話咱們的插件的代碼是處於沒有被保護的暴露狀態的。「jQuery.fn」是「jQuery.prototype」的簡寫,意味當咱們經過jQuery命名空間去獲取咱們的插件的時候,它僅可寫(不可修改)。它事實上能夠爲你乾點什麼事呢?它讓你恰當地組織本身的代碼,和理解如何保護你的代碼不受運行時候不須要的修改。最好的說法就是,這是一個很好的實踐!
經過一個插件,咱們得到一個基本的jQuery函數:
(function($) { $.fn.pluginName = function(options) { // 代碼在此處運行 return this; } })(jQuery);
上面的代碼中的函數能夠像其餘的jQuery函數那樣經過「$(‘#element’).pluginName()」來調用。注意,我是如何把「return this」語句加進去的;這小片的代碼經過返回一個原來元素的集合(包含在this當中)的引用來產生鏈式調用的效果,而這些元素是被一個jQuery對象所包裹的。你也應該注意,「this」在這個特定的做用域中是一個jQuery對象,至關於「$(‘#element’)」。
根據返回的對象,咱們能夠總結出,在上面的代碼中,使用「$(‘#element’).pluginName()」的效果和使用「$(‘#element’)」的效果是同樣的。在你的即時執行函數做用域中,不必用「$(this)」的方式來把this包裹到一個jQuery對象中,由於this自己已是被包裝好的jQuery對象。
3. 多個元素:理解Sizzle
jQuery使用的選擇器引擎叫Sizzle,Sizzle能夠爲你的函數提供多元素操做(例如對全部類名相同的元素)。這是jQuery幾個優秀的特性之一,但這也是你在開發插件過程當中須要考慮的事情。即便你不許備爲你的插件提供多元素支持,但爲這作準備仍然是一個很好的實踐。
這裏我添加了一小段代碼,它讓你的插件代碼爲多元素集合中每一個元素單獨地起做用:
function($) { // 向jQuery中被保護的「fn」命名空間中添加你的插件代碼,用「pluginName」做爲插件的函數名稱 $.fn.pluginName = function(options) { // 返回「this」(函數each()的返回值也是this),以便進行鏈式調用。 return this.each(function() { // 此處運行代碼,能夠經過「this」來得到每一個單獨的元素 // 例如: $(this).show(); var $this = $(this); }); } })(jQuery);
在以上示例代碼中,我並非用 each()在個人選擇器中每一個元素上運行代碼。在那個被 each()調用的函數的局部做用域中,你能夠經過this來引用每一個被單獨處理的元素,也就是說你能夠經過$(this)來引用它的jQuery對象。在局部做用域中,我用$this變量存儲起jQuery對象,而不是每次調用函數的時候都使用$(this),這會是個很好的實踐。固然,這樣作並不老是必要的;但我已經額外把它包含在個人代碼中。還有要注意的是,咱們將會對每一個單獨方法都使用 each(),這樣到時咱們就能夠返回咱們須要的值,而不是一個jQuery對象。
下面是一個例子,假如咱們的插件支持一個 val 的方法,它能夠返回咱們須要的值:
$('#element').pluginName('val'); // 會返回咱們須要的值,而不是一個jQuery對象
4. 功能:公有方法和私有方法
一個基本的函數可能在某些狀況下能夠良好地工做,可是一個稍微複雜一點的插件就須要提供各類各樣的方法和私有函數。你可能會使用不一樣的命名空間去爲你的插件提供各類方法,可是最好不要讓你的源代碼由於多餘的命名空間而變得混亂。
下面的代碼定義了一個存儲公有方法的JSON對象,以及展現瞭如何使用插件中的主函數中去判斷哪些方法被調用,和如何在讓方法做用到選擇器每一個元素上。
(function($) { // 在咱們插件容器內,創造一個公共變量來構建一個私有方法 var privateFunction = function() { // code here } // 經過字面量創造一個對象,存儲咱們須要的共有方法 var methods = { // 在字面量對象中定義每一個單獨的方法 init: function() { // 爲了更好的靈活性,對來自主函數,並進入每一個方法中的選擇器其中的每一個單獨的元素都執行代碼 return this.each(function() { // 爲每一個獨立的元素建立一個jQuery對象 var $this = $(this); // 執行代碼 // 例如: privateFunction(); }); }, destroy: function() { // 對選擇器每一個元素都執行方法 return this.each(function() { // 執行代碼 }); } }; $.fn.pluginName = function() { // 獲取咱們的方法,遺憾的是,若是咱們用function(method){}來實現,這樣會毀掉一切的 var method = arguments[0]; // 檢驗方法是否存在 if(methods[method]) { // 若是方法存在,存儲起來以便使用 // 注意:我這樣作是爲了等下更方便地使用each() method = methods[method]; // 若是方法不存在,檢驗對象是否爲一個對象(JSON對象)或者method方法沒有被傳入 } else if( typeof(method) == 'object' || !method ) { // 若是咱們傳入的是一個對象參數,或者根本沒有參數,init方法會被調用 method = methods.init; } else { // 若是方法不存在或者參數沒傳入,則報出錯誤。須要調用的方法沒有被正確調用 $.error( 'Method ' + method + ' does not exist on jQuery.pluginName' ); return this; } // 調用咱們選中的方法 // 再一次注意咱們是如何將each()從這裏轉移到每一個單獨的方法上的 return method.call(this); } })(jQuery);
注意我把 privateFunction 當作了一個函數內部的全局變量。考慮到全部的代碼的運行都是在插件容器內進行的,因此這種作法是能夠被接受的,由於它只在插件的做用域中可用。在插件中的主函數中,我檢驗了傳入參數所指向的方法是否存在。若是方法不存在或者傳入的是參數爲對象, init 方法會被運行。最後,若是傳入的參數不是一個對象而是一個不存在的方法,咱們會報出一個錯誤信息。
樣要注意的是,我是如何在每一個方法中都使用 this.each() 的。當咱們在主函數中調用 method.call(this) 的時候,這裏的 this 事實上就是一個jQuery對象,做爲 this 傳入每一個方法中。因此在咱們方法的即時做用域中,它已是一個jQuery對象。只有在被 each()所調用的函數中,咱們纔有必要將this包裝在一個jQuery對象中。
下面是一些用法的例子:
/* 注意這些例子能夠在目前的插件代碼中正確運行,並非全部的插件都使用一樣的代碼結構 */ // 爲每一個類名爲 ".className" 的元素執行init方法 $('.className').pluginName(); $('.className').pluginName('init'); $('.className').pluginName('init', {}); // 向init方法傳入「{}」對象做爲函數參數 $('.className').pluginName({}); // 向init方法傳入「{}」對象做爲函數參數 // 爲每一個類名爲 「.className」 的元素執行destroy方法 $('.className').pluginName('destroy'); $('.className').pluginName('destroy', {}); // 向destroy方法傳入「{}」對象做爲函數參數 // 全部代碼均可以正常運行 $('.className').pluginName('init', 'argument1', 'argument2'); // 把 "argument 1" 和 "argument 2" 傳入 "init" // 不正確的使用 $('.className').pluginName('nonexistantMethod'); $('.className').pluginName('nonexistantMethod', {}); $('.className').pluginName('argument 1'); // 會嘗試調用 "argument 1" 方法 $('.className').pluginName('argument 1', 'argument 2'); // 會嘗試調用 "argument 1" ,「argument 2」方法 $('.className').pluginName('privateFunction'); // 'privateFunction' 不是一個方法
在上面的例子中屢次出現了 {} ,表示的是傳入方法中的參數。在這小節中,上面代碼能夠能夠正常運行,可是參數不會被傳入方法中。繼續閱讀下一小節,你會知道如何向方法傳入參數。
5. 設置插件:傳入參數
許多插件都支持參數傳入,如配置參數和回調函數。你能夠經過傳入JS鍵值對對象或者函數參數,爲方法提供信息。若是你的方法支持多於一個或兩個參數,那麼沒有比傳入對象參數更恰當的方式。
(function($) { var methods = { init: function(options) { // 在每一個元素上執行方法 return this.each(function() { var $this = $(this); // 建立一個默認設置對象 var defaults = { propertyName: 'value', onSomeEvent: function() {} } // 使用extend方法從options和defaults對象中構造出一個settings對象 var settings = $.extend({}, defaults, options); // 執行代碼 }); } }; $.fn.pluginName = function() { var method = arguments[0]; if(methods[method]) { method = methods[method]; // 咱們的方法是做爲參數傳入的,把它從參數列表中刪除,由於調用方法時並不須要它 arguments = Array.prototype.slice.call(arguments, 1); } else if( typeof(method) == 'object' || !method ) { method = methods.init; } else { $.error( 'Method ' + method + ' does not exist on jQuery.pluginName' ); return this; } // 用apply方法來調用咱們的方法並傳入參數 return method.apply(this, arguments); } })(jQuery);
正如上面所示,一個「options」參數被添加到方法當中,和「arguments」也被添加到了主函數中。若是一個方法已經被聲明,在參數傳入方法以前,調用那個方法的參數會從參數列表中刪除掉。我用了「apply()」來代替了「call()」,「apply()」本質上是和「call()」作着一樣的工做的,但不一樣的是它容許參數的傳入。這種結構也容許多個參數的傳入,若是你願意這樣作,你也能夠爲你的方法修改參數列表,例如:「init:function(arg1, arg2){}」。
若是你是使用JS對象做爲參數傳入,你可能須要定義一個默認對象。一旦默認對象被聲明,你可使用「$.extend」來合併參數對象和默認對象中的值,以造成一個新的參數對象來使用(在咱們的例子中就是「settings」);
這裏有一些例子,用來演示以上的邏輯:
var options = { customParameter: 'Test 1', propertyName: 'Test 2' } var defaults = { propertyName: 'Test 3', onSomeEvent: 'Test 4' } var settings = $.extend({}, defaults, options); /* settings == { propertyName: 'Test 2', onSomeEvent: 'Test 4', customParameter: 'Test 1' } */
6. 保存設置:添加持久性數據
有時你會想在你的插件中保存設置和信息,這時jQuery中的「data()」函數就能夠派上用場了。它在使用上是很是簡單的,它會嘗試獲取和元素相關的數據,若是數據不存在,它就會創造相應的數據並添加到元素上。一旦你使用了「data()」來爲元素添加信息,請確認你已經記住,當再也不須要數據的時候,用「removeData()」來刪除相應的數據。
// Shawn Khameneh // ExtraordinaryThoughts.com (function($) { var privateFunction = function() { // 執行代碼 } var methods = { init: function(options) { // 在每一個元素上執行方法 return this.each(function() { var $this = $(this); // 嘗試去獲取settings,若是不存在,則返回「undefined」 var settings = $this.data('pluginName'); // 若是獲取settings失敗,則根據options和default建立它 if(typeof(settings) == 'undefined') { var defaults = { propertyName: 'value', onSomeEvent: function() {} } settings = $.extend({}, defaults, options); // 保存咱們新建立的settings $this.data('pluginName', settings); } else { / 若是咱們獲取了settings,則將它和options進行合併(這不是必須的,你能夠選擇不這樣作) settings = $.extend({}, settings, options); // 若是你想每次都保存options,能夠添加下面代碼: // $this.data('pluginName', settings); } // 執行代碼 }); }, destroy: function(options) { // 在每一個元素中執行代碼 return $(this).each(function() { var $this = $(this); // 執行代碼 // 刪除元素對應的數據 $this.removeData('pluginName'); }); }, val: function(options) { // 這裏的代碼經過.eq(0)來獲取選擇器中的第一個元素的,咱們或獲取它的HTML內容做爲咱們的返回值 var someValue = this.eq(0).html(); // 返回值 return someValue; } }; $.fn.pluginName = function() { var method = arguments[0]; if(methods[method]) { method = methods[method]; arguments = Array.prototype.slice.call(arguments, 1); } else if( typeof(method) == 'object' || !method ) { method = methods.init; } else { $.error( 'Method ' + method + ' does not exist on jQuery.pluginName' ); return this; } return method.apply(this, arguments); } })(jQuery);
在上面的代碼中,我檢驗了元素的數據是否存在。若是數據不存在,「options」和「default」會被合併,構建成一個新的settings,而後用「data()」保存在元素中。