摘要: 本文由簡到繁地介紹了以jQuery作為藍本的js架構開發步聚, 希望藉助本文大家對jQuery這樣的架構內部有一個大致的認識。
隨著時代發展,javascript陣營裡面出現了越來越多的優秀的架構,大大簡化了我們的開發工作,在我們使用這些架構的時候是不是也應該飲水思源想想它們都是怎樣構建起來的呢?如果你不滿足於僅僅是使用一些現成的API,而是深入瞭解它們內部的實現機制(照某人的說法, API是貶值最快的東西),最好的辦法就是閱讀它們的原始碼了,前提是你讀得懂。
最近兩天研究了一下jQuery的源碼,在這裡將本人一些粗淺認識分享出來,不當之處請各位指正。好了,下面我們就來看看jQuery大概是怎樣工作的,我假定你已經具備了一些基本的javascript知識,如果基礎不夠俺推薦你閱讀《JavaScript進階程式設計》和《悟透JavaScript》這兩本書。本文不適合對js裡面的類、對象、函數、prototype等概念沒有瞭解的朋友。
我們從最開始的說起:
首先構造一個對象給使用者,假定我們這個架構叫 Shaka ( 俺的名字;) )
var Shaka = function(){}; 這裡我們建立了一個空函數,裡面什麼也沒有,這個函數實際上就是我們的建構函式。為了讓我們產生的對象能夠調用在prototype裡定義出來的方法, 我們需要用原型的方式(把Shaka當作是一個類)給Shaka添加一些方法,於是定義:
Shaka.fn = Shaka.prototype = {}; 這裡的Shaka.fn相當於Shaka.prototype的別名,方便以後使用,它們指向同一個引用。
OK,我們添加一個sayHello的方法, 給Shaka添加一個參數,這樣這個架構最基本的樣子已經有了,如果它有生命的話那麼它現在是1歲, 看代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"><br /><html><br /><head><br /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><br /></head><br /><body><br /><br /></body><br /></html><br />
[Ctrl+A 全部選擇 提示:你可先修改部分代碼,再按運行]
好啦,先別激動, 我們注意到這個架構跟jQuery在使用上是有一些差別的, 比如在jq 中我們可以這樣寫
jQuery('#myid').someMethod(); 這是怎樣做到的呢, 也就是說 jQuery()這個建構函式返回了一個jQuery的對象執行個體,因此我們可以在上面調用它的方法,所以Shaka的建構函式應該返回一個執行個體,它看起來應該是這個樣子:
var Shaka = function(){ return //返回Shaka的執行個體; }; 那麼我們要如何取得一個Shaka的執行個體呢, 我們先來回顧一下使用prototype方式來類比類的時候 var someObj = new MyClass(); 這個時候實際上是建立一個新對象someObje,把新對象作為this指標,調用 MyClass函數,即類的建構函式, 然後 someObj 就獲得了在 MyClass.prototype裡面定義的方法, 這些方法內的this指標指當前對象執行個體。
在jQuery中使用了一個Factory 方法來建立一個執行個體,這個方法位於jQuery.prototype中, 現在我們重新來定義Shaka.prototype, 給它添加一個init方法用於返回一個Shaka的執行個體, 並且把Shaka的建構函式稍稍改變一下:
var Shaka = function(age) { return new Shaka.fn.init(age); };
Shaka.fn = Shaka.prototype = {
init: function(age) { this.age = age; return this; },
sayHello: function() { alert('I am a little baby, my age is ' + this.age + ' years old.'); }
};
Shaka.fn.init.prototype = Shaka.fn;//這裡new Shaka.fn.init(age)建立的對象具有init方法的prototype指向對象的方法 , 因此我們將init方法的prototype指向 Shaka的prototype, 這樣建立出來的對象就具有了Shaka.prototype裡面定義的方法。
OK,現在我們的小寶寶變成大一點的寶寶了,打個招呼先:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"><br /><html><br /><head><br /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><br /></head><br /><body><br /><br /></body><br /></html><br />
[Ctrl+A 全部選擇 提示:你可先修改部分代碼,再按運行]
嗯,好象有點樣子了,但是光這樣還不行,來點實際的, 我們在新架構中實現jquery裡val()方法的部分功能,這個方法不加參數調用時返回指定ID的input的值,加參數時為設定這個input的值,與jQery一樣,我們約定使用id來尋找對象時使用"#"符號。把要尋找的目標ID作為建構函式的參數傳進去,我們給Shaka.prototype添加一個val()方法, 給Shaka添加一個selector的屬性用於儲存我們要尋找的目標。:
Shaka.fn = Shaka.prototype = {
init: function(selector) { this.selector = selector; return this; },
val: function(newValue) { //方法實現代碼 }
};
var Shaka = function(selector) { return new Shaka.fn.init(selector); };
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"><br /><html><br /><head><br /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><br /></head><br /><body><br /><form method="post" action="" name="myform"><br />我幾歲了? <br /><br /><input id="myInput" type="text" value="Hello world!" size="50" /><br /></form><br /><br /></body><br /></html><br />
[Ctrl+A 全部選擇 提示:你可先修改部分代碼,再按運行]
休息一下,順便插播一條廣告: 俺的blog http://darkangle.cnblogs.com
到目前為止我們已經建立一個可以工作的架構雛形,為了使程式可以更方便地被調用,比如jQuery可以使用$符號來簡寫,我們也弄一個,在此之前我們先來回顧兩個東西:
1. 我們在指令碼中可以這樣定義變數:
var foo = 'someThing';
bar = 'otherthing';
這樣兩種寫法都是合法的,但是意義完全不同, 第一個語句建立了一個新的變數,而第二個是定義了window對象的一個屬性,相當於window.bar = 'otherthing';, 因此我們想使我們的Shaka具有這樣的調用方式能力: $.someMethod();就需要將Shaka設定為window的一個屬性, 於是我們的Shaka建構函式就得寫成這樣:
var Shaka = window.Shaka = window.$ = function(selector) { return new Shaka.fn.init(selector); };
2. javascript的匿名函數.
建立並執行一個匿名函數的基本形式: (function(){ alert('Hello World!'); })(); 為什麼要用到匿名函數呢,因為我們不想把Shaka的內部實現暴露出來,這樣容易與其它代碼衝突,只提供一個單一的入口,我們可以這樣測試一下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"><br /><html><br /><head><br /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><br /></head><br /><body><br /><br /></body><br /></html><br />
[Ctrl+A 全部選擇 提示:你可先修改部分代碼,再按運行]
然後,還有一個問題需要解決,俺們的架構做出來了但是還很簡陋,在這之前我們需要讓它與其它的架構協同工作,因此帶來一個問題, 如果我們都使用$作為簡寫形式就會衝突了, 象jQuery一樣,我們需要提供一個noConfilit的方法“出讓”$的使用權。在我們的程式最開始處加入下面的代碼:
var _$ = window.$;
意思是將此前定義的$對象引用放到 _$ 中, 然後我們再給Shaka擴充一個方法出來, 如果其它開發人員需要自行擴充的話也可以使用這個方式(jQuery的extend方法提供了更為強大的功能,請大家自行研究):
(function($){ //extend method definition. })(Shaka);
意思是將Shaka作為這個匿名函數的參數來調用這個方法。
前面我們講過 Shaka.fn 就是 Shaka.prototype 的別名,因此我們要在Shaka.prototype 裡面添加新的方法就可以寫成這樣
(function($){
$.fn.noConflict = function(){
window.$ = _$;//把$還給在開始處取得的引用.
};
})(Shaka);
現在我們來看一個完整的:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"><br /><html><br /><head><br /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><br /></head><br /><body><br /><form method="post" action="" name="myform"><br /><h1> 我幾歲了?</h1> <br /><br /><input id="myInput" type="text" value="Hello world!" size="50" /><br /><br /><br /><br /><input id="otherInput" type="text" size="50" /><br /></form><br /><br /></body><br /></html><br />
[Ctrl+A 全部選擇 提示:你可先修改部分代碼,再按運行]
現在好象不錯了,我們的Shaka baby已經5歲了;) , 當然這還只是個簡陋的東西,要實現健壯而強大的功能還需要付出很多努力, 希望諸位爹媽把自己的孩子培養成人才, good luck!