<b>2.2 總體結構</b>
構造jquery對象子產品的總體源碼結構如代碼清單2-1所示。
代碼清單2-1 構造 jquery 對象子產品的總體源碼結構
16 (function(
window, undefined ) {
// 構造 jquery 對象
22 var jquery = (function() {
25 var jquery = function( selector, context
) {
27 return new jquery.fn.init(
selector, context, rootjquery );
28 },
// 一堆局部變量聲明
97 jquery.fn = jquery.prototype = {
98 constructor: jquery,
99 init: function( selector, context,
rootjquery ) { ... },
// 一堆原型屬性和方法
319 };
322 jquery.fn.init.prototype = jquery.fn;
324 jquery.extend = jquery.fn.extend =
function() { ... };
388 jquery.extend({
// 一堆靜态屬性和方法
892 });
955 return jquery;
957
})();
// 省略其他子產品的代碼
9246 window.jquery = window.$ = jquery;
9266 })( window );
下面簡要梳理下這段源碼。
第16~9266行是最外層的自調用匿名函數,第1章中介紹過,當jquery初始化時,這個自調用匿名函數包含的所有javascript代碼将被執行。
第22行定義了一個變量jquery,第22~957行的自調用匿名函數傳回jquery構造函數并指派給變量jquery,最後在第9246行把這個jquery變量暴露給全局作用域window,并定義了别名$。
在第22~957行的自調用匿名函數内,第25行又定義了一個變量jquery,它的值是jquery構造函數,在第955行傳回并指派給第22行的變量jquery。是以,這兩個jquery變量是等價的,都指向jquery構造函數,為了友善描述,在後文中統一稱為構造函數jquery()。
第97~319行覆寫了構造函數jquery()的原型對象。第98行覆寫了原型對象的屬性constructor,使它指向jquery構造函數;第99行定義了原型方法jquery.fn.init(),它負責解析參數selector和context的類型并執行相應的查找;在第27行可以看到,當我們調用jquery構造函數時,實際傳回的是jquery.fn.init()的執行個體;此外,還定義了一堆其他的原型屬性和方法,例如,selector、length、size()、toarray()等。
第322行用jquery構造函數的原型對象jquery.fn覆寫了jquery.fn.init()的原型對象。
第324行定義了jquery.extend()和jquery.fn.extend(),用于合并兩個或多個對象的屬性到第一個對象;第388~892行執行jquery.extend()在jquery構造函數上定義了一堆靜态屬性和方法,例如,noconflict()、isready、readywait、holdready()等。
看上去代碼清單2-1所述的總體源碼結構有些複雜,下面把疑問和難點一一羅列,逐個分析。
1)為什麼要在構造函數jquery()内部用運算符new建立并傳回另一個構造函數的執行個體?
通常我們建立一個對象或執行個體的方式是在運算符new後緊跟一個構造函數,例如,newdate()會傳回一個date對象;但是,如果構造函數有傳回值,運算符new所建立的對象會被丢棄,傳回值将作為new表達式的值。
jquery利用了這一特性,通過在構造函數jquery()内部用運算符new建立并傳回另一個構造函數的執行個體,省去了構造函數jquery()前面的運算符new,即我們建立jquery對象時,可以省略運算符new直接寫jquery()。
為了拼寫更友善,在第9246行還為構造函數jquery()定義了别名$,是以,建立jquery對象的常見寫法是$()。
2)為什麼在第97行執行jquery.fn
= jquery.prototype,設定jquery.fn指向構造函數jquery()的原型對象jquery.prototype?
jquery.fn是jquery.prototype的簡寫,可以少寫7個字元,以友善拼寫。
3)既然調用構造函數jquery()傳回的jquery對象實際上是構造函數jquery.fn.init()的執行個體,為什麼能在構造函數jquery.fn.init()的執行個體上調用構造函數jquery()的原型方法和屬性?例如,$('#id').length和$('#id').size()。
在第322行執行jquery.fn.init.prototype = jquery.fn時,用構造函數jquery()的原型對象覆寫了構造函數jquery.fn.init()的原型對象,進而使構造函數jquery.fn.init()的執行個體也可以通路構造函數jquery()的原型方法和屬性。
4)為什麼要把第25~955行的代碼包裹在一個自調用匿名函數中,然後把第25行定義的構造函數jquery()作為傳回值指派給第22行的jquery變量?去掉這個自調用匿名函數,直接在第25行定義構造函數jquery()不也可以嗎?去掉了不是更容易閱讀和了解嗎?
去掉第25~955行的自調用匿名函數當然可以,但會潛在地增加構造jquery對象子產品與其他子產品的耦合度。在第25~97行之間還定義了很多其他的局部變量,這些局部變量隻在構造jquery對象子產品内部使用。通過把這些局部變量包裹在一個自調用匿名函數中,實作了高内聚低耦合的設計思想。
5)為什麼要覆寫構造函數jquery()的原型對象jquery.prototype?
在原型對象jquery.prototype上定義的屬性和方法會被所有jquery對象繼承,可以有效減少每個jquery對象所需的記憶體。事實上,jquery對象隻包含5種非繼承屬性,其餘都繼承自原型對象jquery.prototype;在構造函數jquery.fn.init()中設定了整型屬性、length、selector、context;在原型方法.pushstack()中設定了prevobject。是以,也不必因為jquery對象帶有太多的屬性和方法而擔心會占用太多的記憶體。
關于構造函數、原型、繼承等基礎知識,請查閱相關的基礎類書籍。