在我前一篇部落格裡有位童鞋問了xQuery.fn.init.prototype = xQuery.fn;寫法的目的,這個問題在我臨摹jQuery時候也碰到過,最開始我是沒有加入這個方法,在firebug裡面,方法console.log(xQuery('').xquery);列印出的結果是:undefined,這就表明xquery屬性根本就沒有定義,我構建的xQuery對象只是保留了在xQuery.fn.init(selector,context);裡面存在的屬性,如是我做了下面的測試:
xQuery.fn = xQuery.prototype = {init:function(){return this;},tempquery:'1.0.1',length:23,size:function(){return this.length;}};console.log(xQuery().tempquery);//undefinedconsole.log(xQuery().length);//undefinedconsole.log(xQuery().size());//error:xQuery().size is not a function
那麼xQuery.fn.init.prototype = xQuery.fn;作用到底是怎樣的?我做了下面一系列的測試:
測試一:對於代碼:
var xQuery = function(selector,context){return new xQuery.fn.init(selector,context);}
這是使用了javascript裡面的原廠模式構建對象,只不過這個工廠比較特別,讓人第一眼覺得是個迭代的調用的原廠模式,因此我寫了下面的測試代碼:
var xQuery = function(){return new tempQuery();};var tempQuery = function(){};tempQuery.prototype = {tempquery:'1.0.1',length:4,size:function(){return this.length;}};console.log(xQuery().tempquery);//1.0.1console.log(xQuery().length);//4console.log(xQuery().size());//4console.log(xQuery() instanceof xQuery);//false
這裡我另起了一個對象tempQuery ,初始化了tempQuery 的原型鏈的屬性,那麼每次調用xQuery()就是在使用作為xQuery建構函式傳回值的tempQuery 對象。如果這麼做,就存在了javascript裡原廠模式的缺點了:1.會產生重複的相似對象,造成系統資源的浪費;2.xQuery對象識別的問題(例如:console.log(xQuery() instanceof xQuery);//false)。
要解決這些問題,那麼xQuery的建構函式應該是構造自己本身,而不是每次構造都是一個新的對象,這樣的思路就能避免javascript原廠模式的缺陷,因此我又做了下面測試。
測試二:
var xQuery = function(){ returnnew xQuery();//too much recursion new xQuery();//too much recursion,無限遞迴調用 };xQuery.prototype = {tempquery:'1.0.1',length:8,size:function(){return this.length;}};console.log(xQuery().tempquery);console.log(xQuery().length);console.log(xQuery().size());console.log(xQuery() instanceof xQuery);
最終的結果是會在new xQuery();代碼處報出too much recursion new xQuery();//too much recursion,無限遞迴調用 的錯誤,也就是所謂的記憶體溢出了。這種寫法真白癡,如果不是為了驗證結果,我不會去寫這麼明顯的遞迴調用的代碼的。
那麼有什麼更好的寫法了,如是我再看看jQuery源碼,發現它是使用到了prototype原型鏈來構造xQuery對象。因此就有下面測試代碼:
測試三:
var xQuery = function(){return new xQuery.prototype.init();};xQuery.prototype = {init:function(){return this;},tempquery:'1.0.1',length:8,size:function(){return this.length;}};console.log(xQuery().tempquery);//undefinedconsole.log(xQuery().length);//undefinedconsole.log(xQuery() instanceof xQuery);//falseconsole.log(xQuery().size());//xQuery().size is not a function
這種寫法就沒有報記憶體溢出的錯誤了,不過也沒有達到我們預期的效果xQuery().tempquery和xQuery.length都是undefined,xQuery() 的類型也不是xQuery,size()還沒有定義(這個我和文章前面沒寫xQuery.fn.init.prototype = xQuery.fn;的情況一樣),為什麼會有這樣的結果了?其實從javascript對象的理論老理解這種結果就很簡單了。原因分析如下:
我們知道javascript裡面除了string,boolean等基本類型外其他都是object(對象),而對象的名稱只是該對象的在棧記憶體中的地址,換種說法就是該對象的引用。其實我們程式中的return new xQuery.prototype.init();這個只是調用了xQuery.prototype.init();這個引用的所對應的對象,其他的對象裡的屬性都沒管,也就是說代碼裡的:
size:function(){return this.length;}
還沒有被調用過,所以我們看到報了size is not a function的錯誤。呵呵,是不是被我說糊塗了啊,其實這裡我們還要瞭解一個javascript裡面的一個知識,javascript是一個指令碼語言,他和C、java在編譯,運行上有很大不同。javascript裡面如果我們定義一個function,例如:
var ftn = function(){ alert('Hello World');}
頁面載入時候,瀏覽器裡面的javascript解譯器只是生命了ftn變數,這個過程叫做javasript的先行編譯,這時候的ftn = undefined,當我們new ftn();時候,也就是我們運行js代碼時候,javascript解譯器是先編譯再執行,而return new xQuery.prototype.init();只是運行了init()對象,而size還沒運行,所以運行結果就是size is not a function的錯誤。
那麼要解決這個問題,我們只要在return new xQuery.prototype.init();時候同時讓xQuery.prototype其他代碼也運行起來,那麼上面的問題不是就解決了嗎?
如是我做了第四個測試。
測試四:
var xQuery = function(){return new xQuery.prototype.init();};xQuery.prototype = {init:function(){return this;},tempquery:'1.0.1',length:8,size:function(){return this.length;}};xQuery.prototype.init.prototype = xQuery.prototype;console.log(xQuery().tempquery);//1.0.1console.log(xQuery().length);//8console.log(xQuery() instanceof xQuery);//trueconsole.log(xQuery().size());//8
哈哈,這不就是jQuery的效果啊。我把xQuery.prototype.init引用對應的對象的prototype原型鏈指向了xQuery.prototype,那麼構建 new xQuery.prototype.init();
時候xQuery.prototype 也同樣被構造了,那麼我在init裡面修改和init平級的屬性,效果如何了?
測試五:
var xQuery = function(){return new xQuery.prototype.init();};xQuery.prototype = {init:function(){this.length = 10001;this.tempquery = '1.6.1';return this;},tempquery:'1.0.1',length:8,size:function(){return this.length;}};xQuery.prototype.init.prototype = xQuery.prototype;
結果:
console.log(xQuery().tempquery);//1.6.1console.log(xQuery().length);//10001console.log(xQuery() instanceof xQuery);//trueconsole.log(xQuery().size());//10001
這就說明this指標指向的對象是同一個xQuery對象,表明xQuery.prototype也被構建了。不過上面的寫法似乎和jQuery的寫法還是有所不同,不過效果一樣了哈。
jQuery裡面的jQuery.fn真是沒啥好說的,只是jQuery的開發人員覺得jQuery.prototype太長了為了節省代碼而將jQuery.fn作為jQuery.prototype的別名,下面我把代碼修改成jQuery的樣式,最終代碼如下:
var xQuery = function(){return new xQuery.prototype.init();};xQuery.fn = xQuery.prototype = {init:function(){this.length = 10001;this.tempquery = '1.6.1';return this;},tempquery:'1.0.1',length:8,size:function(){return this.length;}};xQuery.fn.init.prototype = xQuery.fn;console.log(xQuery().tempquery);//1.6.1console.log(xQuery().length);//10001console.log(xQuery() instanceof xQuery);//trueconsole.log(xQuery().size());//10001
總結下吧:本來我想說這就是jQuery架構的核心,寫完後發現其實不然,所以這裡的總結只是說jQuery源碼裡的代碼技巧性很高,我下篇部落格打算暫停下分析jQuery源碼,而將一些基礎知識系統的講解下,javascript是一個常常讓人蒙掉的語言,就是它有很多非常規詭異的文法,或許這些大夥都清楚,但是時不時的溫故而知新還是相當有好處的。