每一個可以努力的日子,都是一份厚禮。
寫一個 jQuery 插件
工作中發現,我的很多時間都在為前端頁面交互編寫 JavaScript 代碼。相比較而言,由於有 MVC 框架的各種約定,後端的 PHP 代碼寫起來顯得比 JavaScript 要優雅很多。雖然也有 jQuery 這樣的利器(得益於良好的跨瀏覽器兼容特性和簡潔的使用接口,jQuery 幾乎已經成了 JavaScript 的代名詞),但我用到的只是它的選擇器,以及別人寫的一些插件而已。我深知要透徹地理解一門語言,就應該去用它做一個小項目,帶着目標學習需用到的東西才能有的放矢。正好遇到一個交互需求找不到已有解決方案,於是摒棄以前那樣簡單粗暴的寫 js 代碼,我決定自己實現一個 jQuery 插件來搞定它。
JavaScript 是一門混亂的語言,好的特性和壞的特性混雜在一起。而不同瀏覽器對標準的解析不一致,使得這門語言更加混亂,在這種情況下遵循最佳實踐有諸多好處,至少不會掉入坑裡。所以就有了《JavaScript: The Good Parts》這類書專門教最佳實踐。可惜讀完後再去看別人的 js 代碼,會發覺幾乎沒有誰做得很標準。
一、jQuery 插件的類別
在 jQuery 中要使用一個插件,一般有兩種形式:
- 類級別。例如
$.myPlugin()
- 對象級別。例如
$('#node').myPlugin()
類級別插件可以理解為拓展 jQuery 類,即給 jQuery 添加新的全局函數,典型的例子就是 $.ajax()
這個函數。jQuery 的全局函數就是屬於 jQuery 命名空間的函數。另一種是對象級別的插件,即作用於指定的 jQuery 對象,典型的例子 $('.msg').show()
。
二、類級別 jQuery 插件的開發
1. 原始寫法
一般在項目中我們會引入一個 js 文件,裡面存放了所有的 js 代碼。
<script type="text/javascript" src="http://www.lovelucy.info/all.js"></script> |
只要寫一些函數簡單地放在文件里,就算是一個模塊,直接調用就行了。
function m1(){ //... } function m2(){ //... } |
這種方法缺陷很明顯,就是污染了全局空間,無法保證不與其他模塊發生變量名衝突,而且方法成員之間看不出直接關係。
2. 擴展寫法
使用 jQuery.extend(object) 來擴展 jQuery 類本身,可以理解為 jQuery 添加靜態方法。
$.extend({ addMethod : function(a, b){return a + b;} }); // $.addMethod(1, 2); //return 3 |
3. 簡單寫法
給 jQuery 添加一個全局函數,只需如下定義
jQuery.foo = function() { alert('test'); // other code... }; |
這樣就能直接用了:jQuery.foo()
或者 $.foo()
。
4. 使用命名空間
雖然上面的 2 種寫法相對於原始寫法要乾淨很多,減少了在全局空間衝突的概率,但是在 jQuery 命名空間中,仍然不可避免某些函數或變量名可能和其他 jQuery 插件衝突。因此我們習慣再封裝一層,將一些方法封裝到另一個自定義的命名空間。
jQuery.myPlugin = { foo:function() { alert('test'); }, bar:function(param) { alert('test "' + param + '".'); } }; // $.myPlugin.foo(); // $.myPlugin.bar('hello'); |
採用命名空間的函數仍然是全局函數,使用獨立的插件名我們可以避免命名空間內函數的衝突。
三、對象級別 jQuery 插件的開發
大部分 jQuery 插件都是對象級別的,開發一個對象級別插件會遇到閉包這個概念,簡單起見先只看閉包的表現形式
(function($){ // your codes })(jQuery); |
1. 擴展寫法
給 jQuery 對象添加方法,就是對 jQuery.prototype 進行擴展,為 jQuery 類添加成員方法,需要用到 jQuery.fn.extend(object);
。下面是一個例子:
$.fn.extend({ getInputText:function(){ $(this).click(function(){ alert($(this).val()); }); } }); //$("#username").getInputText(); |
2. 通用寫法
對象級別的插件也可以這樣定義 $.fn.myPlugin = function(){}
,同樣的,和類級別相比多了一個 fn。
一個通用的對象級別插件框架——
(function($){ $.fn.myPluginName = function(options){ var defaults = {} //各種屬性和參數 var options = $.extend(defaults, options); this.each(function(){ //插件的實現代碼 }); }; })(jQuery); |
$.extend(defaults, options)
通過合併 defaults 和 options 來擴展默認參數,實現插件接受外部 options 參數的功能。於是我們就可以見到這樣的用法:
$('#myDiv').hilight({ foreground: 'blue' }); |
3. 改進的通用寫法
上面代碼的一種改進是暴露插件的默認設置。這可以讓插件的使用者更容易用較少的代碼覆蓋和修改插件。
(function($){ $.fn.myPluginName = function(options){ var options = $.extend({}, $.fn.myPluginName.defaults, options); this.each(function(){ //插件的實現代碼 }); }; // 暴露的默認參數 $.fn.myPluginName.defaults = {}; })(jQuery); |
於是我們就可以見到這樣使用的
$.fn.hilight.defaults.foreground = 'blue'; $('#myDiv').hilight(); |
覆蓋默認的配置就只需要調用一次,而不必在每次調用插件時都傳遞參數。是否需要傳遞參數,在不同的場景下可以靈活處理,兩者的使用可以結合起來。
更高級的插件寫法還包括暴露一些函數給使用者,讓他們可以覆蓋。另一方面,也可以保持私有函數的私有性。具體的代碼這裡就不多贅述了。學習的最好方式就是閱讀別人的插件代碼。
四、總結
我之前遇到的需求是要讓用戶在表單的一個 input 字段中輸入 json 格式的文本。但是用戶純手工輸入很難寫出標準的 json string,藉助我寫的插件,可以將 input 轉換為一個 key-value 的表格,用戶就很容易輸入了。在表單提交時 key-value 將自動 encode 為 json 回填到原來的字段中進行提交。
插件開源在 GitHub: jQuery Key-Value Json Input Plugin
這是我的第一個 jQuery 插件,當然肯定會有更好的設計方案,只是在實現這個插件的過程中,我慢慢對 jQuery 的閉包,鏈式操作,函數式的設計思想(匿名函數)等有了初步的概念。總結是一個反芻的過程,寫完本文似乎理解又加深了一些。
之前我還和同事討論過,Engineering 就是如此,和 Science 最大的不同就是需要總結、積累。Science 一開始就會給我們灌輸概念、公式、定理,世界就是這個樣子,然後用這些公式、定理去解釋現象,計算結果。而 Engineering 則恰恰相反,是在長期的實踐中發現規律和 Best Practice。如果一開始就搬理論,學生往往不知所云,反而是在累積了很多年工作經驗以後頓悟:“是的,就是這樣,就是這樣”,我在大學本科上《軟件工程》課時這種感覺尤為強烈。總結是必要的,這也是寫 blog 的意義所在。
保持強烈的求知慾,做一個樂於學習和自我提升的人。
這篇文章由lovelucy於2013-01-06 12:56發表在前端開發。你可以訂閱RSS 2.0 也可以發表評論或引用到你的網站。除特殊說明外文章均為本人原創,並遵從署名-非商業性使用-相同方式共享創作協議,轉載或使用請註明作者和來源,尊重知識分享。 |
批評不自由
則讚美無意義
對這塊很有幫助參考,努力學習wp jQuery的插件
補充一點,保險起見,最好在寫好的插件js文件開頭加一個分號。。。
😳 看到你在 segmentfault 上的回答,學習了
訪問我這兒也要掛美國代理么?還是你 VPN 常年都掛着。。?
貌似不用了,不過看了下F12裡面的network,跟google有關的東西掛了:show_ads.js, google_plus的fastbutton;還有一個http://tajs.qq.com的