一般我們去寫一個框架,會采用什么樣的設計呢神帅?比如設計一個jQuery框架律罢,一般我們會創(chuàng)建一個函數(shù)對象
functionjQuery(params){ //構造函數(shù)}
jQuery.prototype.init = function(params){
//初始化操作
}jQuery.prototype.html=function(html){
//實現(xiàn)類似設置innerHTML的操作
}varjq =newjQuery("#id");
jq.init();
jq.html("test");
我想這是我們最常見的使用方式缓待,這種方式需要new一個新的實例婚陪,然后再進行初始化操作族沃,然后才能繼續(xù)操作,很顯然這樣會比較繁瑣而且代碼較多,而jQuery中的使用方法卻非常簡潔:
獲取指定元素:$("#id")/$(".class")/$("body").html("test");
創(chuàng)建html片段$("
").html("test");
設置文檔加載完成后的方法$(function(){XXX});
等等泌参,為何jQuery的使用方法如此簡單脆淹,這是因為jQuery設計的是一種無new的創(chuàng)建方法,這是jQuery獨特的設計思路之一沽一。
我們打開jQuery初始化的這塊代碼盖溺,發(fā)現(xiàn)基本結構是這樣子的(提取主要框架部分源碼):
(function( window, undefined ) {varjQuery =function( selector, context ) {returnnewjQuery.fn.init( selector, context, rootjQuery );
};
jQuery.fn= jQuery.prototype ={
constructor: jQuery,
init:function( selector, context, rootjQuery ) {
if ( !selector ) {
return this;
}
if ( typeof selector === "string" ) {
if ( match && (match[1] || !context) ) {
return this;
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector );
} else {
return this.constructor( context ).find( selector );
}
} else if ( selector.nodeType ) {
return this;
} else if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );
}
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
},
html:function(value){
//實現(xiàn)類似innerHTML操作
}? ? }? ? jQuery.fn.init.prototype= jQuery.fn;
})( window );
1. 首先把代碼封裝在一個匿名函數(shù)里面,防止污染全局環(huán)境铣缠,然后創(chuàng)建一個對象jQuery烘嘱,這個對象接受兩個參數(shù)selector,context,其中selector就是我們通常傳入的 #id,.class等字符串,這個對象函數(shù)里面并沒有做什么邏輯而是直接返回了一個原型上的init方法,然后把參數(shù)傳了進去蝗蛙。
2. init方法接受了3個參數(shù)蝇庭,然后在內部進行了各種判斷,根據不同的場景歼郭,返回一個不同的實例對象,類似一個工廠方法辐棒。
3. 將jQuery.fn.init的原型掛載到jquery的原型上病曾。
4. 將jQuery的constructor還原為jQuery
init方法的內部邏輯我們后面再看,首先看下為何jQuery要這樣做漾根?
首先jQuery在構造函數(shù)里面就執(zhí)行了new方法并執(zhí)行了init方法泰涂,將new和init兩步直接合并成一步,而且自帶new操作辐怕,不需要用戶再去進行new的操作逼蒙,但是為何要使用一個原型上的init方法,而不是在自身自己new出一個對象呢寄疏?
想象一下如果jQuery這樣寫
varjQuery =function( selector, context ) {
//做上面的各種場景判斷returnnewjQuery( selector, context, rootjQuery );
}
這樣寫會存在一個問題是牢,jQuery函數(shù)內部new一個新的jQuery,然后會一直這樣new下去無限循環(huán)陕截,陷入死循環(huán)驳棱。為了防止這種情況的出行,jQuery將這部分邏輯放在了原型中的一個init方法农曲,由init方法來進行工廠方法的處理社搅,return new?jQuery.fn.init( selector, context, rootjQuery ),這樣就避免了死循環(huán)的問題,而且實現(xiàn)了一步到位的初始化形葬。
但是這樣寫還存在一個問題合呐,init方法里面的return this是指向的jQuery的實例對象,那他將無法訪問jQuery的原型對象笙以,比如說jQuery.prototype.html方法淌实,因為html的方法是jQuery.prototype上的所以init方法無法訪問這個函數(shù),為了解決這個問題源织,我們看到之前的源碼有最后一行:
jQuery.fn.init.prototype = jQuery.fn;
其中jQuery.fn就是jQuery.prototpye翩伪,也就等價于
jQuery.prototpye.init.prototype = jQuery.prototpye;
這句話的意思是,把init的方法的原型改為指向jQuery的原型谈息,這樣new init的出來的實例對象也就等價于new jQuery的實例對象缘屹,所以也就可以訪問jQuery的原型方法了。
jQuery.prototype.init和jQuery的原型指向的是同一個對象侠仇,這樣不管是init還是jQuery去修改原型對象都是等價的轻姿,這樣就實現(xiàn)了上面提到的問題。
最后逻炊,jQuery再把constructor還愿回來到jQuery互亮,防止構造函數(shù)指向錯誤,引起調用問題余素,為何要這樣處理呢豹休?因為我們是把jQuery的prototype直接覆蓋成了一個新的實例對象,所以jQuerty的constructor就變成了{}的構造函數(shù)Object,所以需要將constructor還原為真正的jQuery桨吊。
對比下兩種原型改寫方式:
//方式1
jQuery.prototype.html =function(value){}
//方式2jQuery.prototype={
html:function(value){}
}
兩種方式看起來好像沒啥區(qū)別威根,但其實區(qū)別很大,第一種是給jQuery的prototype新增一個方法视乐,這種方式是不會影響jQuery的constructor洛搀,還是function jQuery(),
而第二種方式是直接覆蓋了jQuery的prototype,這種情況下的constructor會被直接改寫為{html:function(value)}這個實例對象的構造函數(shù)佑淀,也就是function Object()
所以需要重置一下jQuery的constructor