jQuery原型技術分解
起源----原型繼承
使用者過javascript的都會明白,在javascript腳本中到處都是函數,函數可以歸置代碼段,把相對獨立的功能封閉在一個函數包中。函數也可以實作類,這個類是面向對象程式設計中最基本的概念,也是最高抽象,定義一個灰就相當于制作一個模型,然後借助這個模型複制無數的執行個體。
例如,下面的就可定義最初的jQuery類,類名就是jQuery,你可以把它視為一個函數,函數名是jQuery。那當然也可以把它視為一個對象,對象名是jQuery。與其也面向對象的程式設計語言比,javascript對于這些概念的界定好像很随意,這降低了程式設計的門檻,反之也降低了javascript作為程式設計語言的層次。
<script language=”javascript” type=”text/javascript”>
var jQuery = function(){
//函數體
}
</script>
上面建立一個空函數,好像什麼都不能夠做,這個函數實際上是所謂的構造函數。構造函數在面向對象語言中是類的一個特殊方法,用來建立類。在javascript中,你可以把任何函數都視為構造函數,這沒有什麼不可以的,這樣不會傷害代碼本身。
所有類都有最基本的功能,如繼承、派生和重寫等。javascript很奇特,它通過所有函數綁定一個prototype屬性,由這個屬性指向一個原型對象,原型對象中可以定義類的繼承屬性和方法,是以,對于上面的空類,可以繼續擴充原型。(繼承請參見js學習筆記---類和子產品)
<script language=”javascript” type=”text/javascript”>
var jQuery = function(){}
jQuery.prototype = {
//擴充原型對象
}
</script>
原型對象是javascript實作繼承的基本機制。如果覺得jQuery.prototype名稱太長,沒有關系,我們可以為其重新命名,如fn,當然你可以随便命名。如果直接命名fn,則表示該名稱屬于Window對象,即全局變量名。更安全的方法是為jQuery類定義一個公共屬性jQuery.fn,然後把jQuery的原型對象傳遞給這個公共屬性,實作代碼如下:
<script language=”javascript” type=”text/javascript”>
var jQuery = function(){}
jQuery.fn=jQuery.prototype= {
//擴充原型對象
}
</script>
這裡jQuery.fn相當于jQuery.prototype的别名,友善以後使用,它們指向一個引用。是以若要調用jQuery的原型方法,直接使用jQuery.fn公共屬性即可,不需要直接引用jQuery.prototype,當然直接使用jQuery.prototype也是可以的。然後原型對象可以使用别名,jQuery類也可以起個别名,我們可以使用$符号來引用它,如下:var $ = jQuery = function(){}
現在模仿jQuery架構源碼,給它添加兩個成員,一個是原型屬性jquery,一個是原型方法size(),如下:
<script language=”javascript” type=”text/javascript”>
var $=jQuery = function(){}
jQuery.fn = jQuery.prototype = {
jquery:”1.10.01” //原型屬性
size:function(){ //原型方法
return this.length;
}
}
</script>
生命------傳回執行個體
上例中的代碼相信很多人都寫過,但是隻有John Regis一個人把它發展成了jQuery這樣牛X的架構。
上面定義了兩個原型成員,這個架構基本的樣子就出來啦,但是該如何調用jquery屬性和size()方法呢?
也許,你可采用如下的方法調用:
<script language=”javascript” type = “text/javascript”>
var my$ = new $();
alert(my$.jquery);
alert(my$.size());
</script>
但是,jQuery不是這樣調用的。它模仿類似下面的方法進行調用。
$().jquery
$().size();
也就是說,我們應該把jQuery看做一個類,同時也應該把它視為一個普通函數,并讓這個函數傳回值為jQuery類的執行個體。是以如下
<script language=”javascript” type=”text/javascript”>
var $=jQuery = function(){
return new jQuery();
}
jQuery.fn = jQuery.prototype = {
jquery:”1.10.01” //原型屬性
size:function(){ //原型方法
return this.length;
}
}
//調用
alert(my$.jquery);
alert(my$.size());
</script>
但是,如果在浏覽器中預覽,則會提示如圖所示的錯誤。記憶體外溢,說明出現了死循環引用。
回一下,當使用var my$ = new$(); 建立jQuery類的執行個體時,this關鍵字就指向對象my$,是以my$執行個體對象就獲得了jQuery.prototype包含的原型屬性或方法,這些方法内this關鍵字就會自動指向my$執行個體對象。是以可以使用一個工廠方法來建立一個執行個體,這個方法放在jQuery.prototype原型對象中,然後在jQuery函數中傳回這個原型方法的調用。代碼如下:
<script language=”javascript”type=”text/javascript”>
var $ = jQuery = function(){
return jQuery.fn.init(); //調用原型方法init();
}
jQuery.fn = jQuery.prototype= {
init:function(){
return this;
}
,jquery:”1.10.01
,size:function(){
return this.length;
}
}
//調用
alert(my$.jquery); //調用屬性,傳回”1.3.2
alert(my$.size()); //調用方法,傳回undefined
</script>
學步-----分隔作用域
我們已經初步實作了讓jQuery函數能夠傳回jQuery執行個體,下面繼續思考:init()方法傳回的是this關鍵字,該關鍵字引用的是jQuery類的執行個體,如果在init()函數中繼續使用this關鍵字,也就是說,假設我們把init()函數也視為一個構造器,則其中的this該如何了解和處理?
例如,在下面的示例中,jQuery原型對象中包含一個length屬性,同時init()從一個普通的函數轉身變成了構造器,它也包含一個length屬性和一個test()方法,運作該示例,我們可以看到,this關鍵字引用init()函數作用域所在的對象,此時它通路length屬性時,傳回0.而this關鍵字也能夠通路一級對象jQury.fn對象的作用域,是以$().jquery傳回”1.3.2”,但是調用$().size()方法時,傳回的是0,而不是1。
<script language=”javascript”type=”text/javascript”>
var $ = jQuery = function(){
return jQuery.fn.init(); //調用原型方法init();
}
jQuery.fn = jQuery.prototype= {
init:function(){
this.length=0;
this.test = function(){
return this.length;
}
return this;
}
,jquery:”1.10.01
,length:1
,size:function(){
returnthis.length;
}
}
//調用
alert($().jquery); //調用屬性,傳回”1.3.2
alert($().test()); //調用方法,傳回0
alert($().size()); //調用方法,傳回0
</script>
這種設計思路很容易破壞作用域的獨立性,對于jQuery這樣的架構來說,很可能會造成消極影響,是以我們可以看到jQuery架構是通過下面的方式調用init()初始化構造函數的
<script language=”javascript”type=”text/javascript”>
var $=jQuery=function(){
return new jQuery.fn.init(); //執行個體化init初始化類型,分隔作用域
}
</script>
這樣就可以把init()構造器中的this和jQuery.fn對象中的this關鍵字隔離開來,避免互相混淆。但是,這種方式也會帶來另一個問題,無法通路,jQuery.fn對象的屬性或方法。如下:
<script language=”javascript”type=”text/javascript”>
var $=jQuery=function(){
return new jQuery.fn.init();
}
jQuery.fn =jQuery.prototype = {
init:function(){
this.length = 0;
this.test= function(){
return this.length;
}
},
jquery:”1.10.01”,
length:1,
size:function(){
return this.length;
}
}
alert($().jquery); //調用屬性,傳回 undefined
alert($().test()); //調用方法,傳回0
alert($().size()); //調用方法,傳回抛出異常
</script>
生長-----跨域通路
如何做到既能分隔始始化構造函數與jQuery原型對象的作用域,又能夠在傳回執行個體中通路jQuery原型對象呢?
jQuery架構巧妙地通過原型傳遞解決了這個問題,它把jQuery.fn傳遞給jQuery.fn.init.prototype,也就是說jQuery的原型對象覆寫init構造器的原型對象,進而實作跨域通路,
<script language=”javascript”type=”text/javascript”>
var $=jQuery=function(){
return new jQuery.fn.init();
}
jQuery.fn =jQuery.prototype = {
init:function(){
this.length = 0;
this.test= function(){
return this.length;
}
},
jquery:”1.10.01”,
length:1,
size:function(){
return this.length;
}
}
jQuery.fn.init.prototype =jQuery.fn; //使用jQuery的原型對象覆寫init的原型對象。
alert($().jquery); //調用屬性,傳回 1.10.01
alert($().test()); //調用方法,傳回0
alert($().size()); //調用方法,0
</script>
成熟------選擇器
jQuery傳回的是jQuery對象,它是一個類數組對象,本質上它就是一個對象,但是它擁有數組的長度和下标,卻沒有繼承數組的方法。
jQuery函數包含兩個參數selector和context,其中selector表示選擇器,而context表示選擇内容範圍,它表示一個DOM元素,為了簡化操作,我們假設選擇的類型僅限定為标簽選擇器。
<div></div>
<div></div>
<div></div>
<script language=”javascript”type=”text/javascript”>
var $=jQuery=function(){
return new jQuery.fn.init();
}
jQuery.fn = jQuery.prototype = {
init:function(selector,context){
selector = selector|| document;
context= context || document;
if(selector.nodeType){
this[0] = selector;
this.length = 1;
this.context = selector;
return this;
}
if(typeof selector = “string”){
var e = context.getElementByTagName(selector);
for(vari=0;i<e.length;i++){
this[i] = e[i];
}
this.length = e.length;
this.context = context;
return this;
}else{
this.length = 0;
this.context = context;
return this;
}
},
jquery:”1.10.01”,
length:1,
size:function(){
return this.length;
}
}
jQuery.fn.init.prototype =jQuery.fn; //使用jQuery的原型對象覆寫init的原型對象。
alert($(“div”).size()); //傳回3
</script>
延續----疊代器
在jQuery架構中,jQuery對象是一個很奇怪的概念,具有多重身份:
第一、 jQuery對象是一個資料集合,它不是一個個體對象,是以,你無法直接使用javascript的方法。
第二、 jQuery對象實際上是一個普通 的對象,因為它是通過new運作符建立的一個新的執行個體對象。它可以繼承原型方法或屬性,同時也擁有Object類型的方法和屬性。
第三、 jQuery對象包含數組特性,因為它指派了數組元素,以數組結構存儲傳回的資料。我們可以以javascript的概念了解jQuery對象,如:
<script language=”javascript” type = “text/javascript”>
var jquery= {
name:”jquery”,
value:”1.2.3”
};
jquery[0] = “jquery”;
jquery[1] = “1.2.3”;
alert(jquery.name); //傳回”jquery”
alert(jquery[0]); //傳回”jquery”
</script>
jQuery對象的結構是按這種形式設計的。可以說,jQuery對象就是對象和數組的混合體,但是它不擁有數組的方法,因為它的數組結構是人為附加的,也就說它不是Array類型的資料,而是Object類型的資料。
第四、 jQuery對象包含的資料都是DOM元素,是通過數組形式存儲的,即通過jQuery[n]形式擷取。同時jQuery對象又定義了幾個模仿Array基本特性的屬性,如length等。
是以jQuery對象是不允許直接操作,隻有分别讀取它包含的每一個DOM元素,才能夠實作各種操作。
延續-----功能擴充
根據一般設計習慣,如果要為jQuery或jQuery.prototype添加函數或方法,可以直接通過點(.)文法實作,或者在jQuery.prototype對象結構中增加一個屬性即可。但是,如果分析jQuery架構的源代碼,你會發現它是通過extend()函數來實作功能擴充的。例如下面兩段代碼都是jQuery架構通過extend()函數來擴充功能的
jQuery.extend({
noConflict:function(deep){},
isFunction:function(obj){},
isArray:function(obj){},
isXMLDoc:function(elem){},
globalEval:function(data){},
})
或者
jQuery.fn.extend({
show:function(speed,callback){},
hide:function(speed,callback){},
toggle:function(fn,fn2){},
fadeTo:function(speed,to,callback){},
animate:function(prop,speed,easing,callback){},
stop:function(clearQueue,gotoEnd){},
})
extend()函數能夠友善使用者快速擴充jQuery架構的功能。但是不會破壞架構的原型結構,進而避免後期人工手動添加工具函數或者方法時破壞jQuery架構的單純性,同時也友善管理。如果不需要某個插件,隻需要簡單地删除即可,而不是需要在jQuery架構源代碼中去篩選和删除。
extend()函數的功能實作起來也很簡單,它隻要把指定對象的方法複制給jQuery對象或jQuery.prototype對象。例如,在下面的示例中,我們為jQuery類和原型定義一個擴充功能函數extend(),該函數的功能很簡單,它能夠把指定參數對象包含的所有屬性複制給jQuery或者jQuery.prototype對象,這樣就可以在應用中随時調用它,并動态擴充jQuery對象的方法。
//jQuery功能擴充函數
jQuery.fn.extend = jQuery.extend =function(obj){
for(var prop in obj) this[prop] =obj[prop];
return this;
}
jQuery架構定義的extend()函數的功能要強大很多,它不僅能夠完成基本的功能擴充,還可以實作對象合并等功能,詳細代碼和解釋如下:
jQuery.extend = jQuery.fn.extend =function(){
//定義複制操作的目标對象
var target = arguments[0]||{},i=1,length=arguments.length,deep = false,options;
//擷取是否深度複制處理
if(typeof target ===”boolean”){
deep = target;
target = arguments[1] || {};
//跳出布爾值和目标對象
i = 2;
}
if(typeof target!=”object” &&!jQuery.isFunction(target)) target = {};
if(length==i){
target = this;
--i;
}
for(;i<length;i++){
//若參數不為null,則進行處理
if((options=arguments[i])!=null){
//extend the baseobject
for(var name in options){ //周遊參數對象
var src =target[name],copy = options[name];
if(target===copy)continue;
//遞歸運算
if(deep&& copy && typeof copy===”object” && !copy.nodeType)
target[name]= jquery.extend(deep,
//不要複制原對象
src || (copy.length!=null?[]:{}),copy);
else if(copy!==undefined)
target[name] = copy;
}
return target;
}
}
}
延續----參數處理
對于參數不固定,傳遞參數的最好辦法是對象直接量。
命名空間
var jQuery =function(){}
jQuery=function(){}
上面所示的代碼是兩種不同的寫法,且都是合法的,但是它們的主義完全不同。第一行代碼聲明了一個變量。而第二行代碼定義了Window對象的一個屬性,也就是說它等同于下面的語句
window.jQuery =function(){};
在全局作用域中,變量和Window對象的屬性可以是相等的,也是可以是互通的,但是當在其他環境中(如局部作用域中),則它們是不相等的,也是無法互通的,是以如果希望jquery具有類似$.method();調用的能力,就需要将jQuery調協為Window對象的一個屬性,是以你就會看到jQuery架構中是這樣定義的。
var jQuery = window.jQuery = window.$ =function(selectio,context){
return new jQuery.fn.init(selector,context);
}
<script language=”javascript” type=text/javascript”>
(function(){
function f1(){
return “f1()”;
}
})();
function f2(){
return “f2()”;
}
alert(f2()); //傳回”f2()”
alert(12()); //抛出異常,禁止通路
</script>
實際上,上面的匿名函數是所謂的閉包,閉包是javascript函數中一個最核心的概念。當然,$和jQuery名字并非是jQuery架構的專利,其他一些經典架構中也會用到$名字,為此jQuery提供了一個noConfilit()方法,該方法能夠實作禁止jQuery架構使用這兩個名字。為了實作這樣的目的,jQuery在架構的最前面,先使用_$和_jQuery臨時寄存這兩個變量的内容,當需要禁用jQuery架構的名字時,可以使用一個臨時變量_$和_jQuery恢複和jQuery這兩個變量的實際内容。如下代碼:
(function(){
var window = this,
undefined,
_jQuery = window.jQuery,
_$ = window.$,
jQuery = window.jQuery = window.$ =function(selector,context){
return newjQuery.fn.init(selector,context);
};
jQuery.fn = jQuery.prototype = {
init:function(selector,context){}
}
})()