原型和閉包是js語言的難點,此文主要講原型及原型實現的繼承,在(二)中會講下閉包,希望對大家有所幫助。若有疑問或不正之處,歡迎提出指正和討論。
一. 原型與建構函式
js所有的函式都有乙個prototype屬性,這個屬性引用了乙個物件,即原型物件,也簡稱原型。這個函式包括建構函式和普通函式,我們講的更多是建構函式的原型,但是也不能否定普通函式也有原型。譬如普通函式:
functionf() alert(f.prototype
instanceof
object)
//true
建構函式,也即構造物件。首先了解下通過建構函式例項化物件的過程。
functiona(x)
varobj
=newa(1
); 例項化obj物件有三步:
1. 建立obj物件:obj=new object();
2. 將obj的內部__proto__指向構造他的函式a的prototype,同時,obj.constructor===a.prototype.constructor(這個是永遠成立的,即使a.prototype不再指向原來的a原型,也就是說:類的例項物件的constructor屬性永遠指向"建構函式"的prototype.constructor),從而使得obj.constructor.prototype指向a.prototype(obj.constructor.prototype===a.prototype,當a.prototype改變時則不成立,下文有遇到)。obj.constructor.prototype與的內部_proto_是兩碼事,例項化物件時用的是_proto_,obj是沒有prototype屬性的,但是有內部的__proto__,通過__proto__來取得原型鏈上的原型屬性和原型方法,firefox公開了__proto__,可以在firefox中alert(obj.__proto__);
3. 將obj作為this去呼叫建構函式a,從而設定成員(即物件屬性和物件方法)並初始化。
當這3步完成,這個obj物件就與建構函式a再無聯絡,這個時候即使建構函式a再加任何成員,都不再影響已經例項化的obj物件了。此時,obj物件具有了x屬性,同時具有了建構函式a的原型物件的所有成員,當然,此時該原型物件是沒有成員的。
原型物件初始是空的,也就是沒有乙個成員(即原型屬性和原型方法)。可以通過如下方法驗證原型物件具有多少成員。
varnum=0;
for(o
ina.prototype) alert(
"member: "+
num);
//alert出原型所有成員個數。
但是,一旦定義了原型屬性或原型方法,則所有通過該建構函式例項化出來的所有物件,都繼承了這些原型屬性和原型方法,這是通過內部的_proto_鏈來實現的。
譬如a.prototype.say=function();
那所有的a的物件都具有了say方法,這個原型物件的say方法是唯一的副本給大家共享的,而不是每乙個物件都有關於say方法的乙個副本。
二. 原型與繼承
首先,看個簡單的繼承實現。
1function
a(x)
4function
b(x,y)
這兩種方法都有將this傳遞到a的執行裡,this指向的是b的物件,這就是為什麼不直接a(x)的原因。這種繼承方式即是類繼承(js沒有類,這裡只是指建構函式),雖然繼承了a構造物件的所有屬性方法,但是不能繼承a的原型物件的成員。而要實現這個目的,就是在此基礎上再新增原型繼承。
通過下面的例子,就能很深入地了解原型,以及原型參與實現的完美繼承。(本文核心在此^_^)
1function
a(x)
4a.prototype.a ="
a"; 5
function
b(x,y)
9b.prototype.b1
=function
() 12
b.prototype
=new
a();
13b.prototype.b2
=function
() 16
b.prototype.constructor
=b;
17var
obj
=newb(1
,3);
這個例子講的就是b繼承a。第7行類繼承:a.call(
this
.x);上面已講過。實現原型繼承的是第12行:b.prototype = new a();
就是說把b的原型指向了a的1個例項物件,這個例項物件具有x屬性,為undefined,還具有a屬性,值為"a"。所以b原型也具有了這2個屬性(或者說,b和a建立了原型鏈,b是a的下級)。而因為方才的類繼承,b的例項物件也具有了x屬性,也就是說obj物件有2個同名的x屬性,此時原型屬性x要讓位於例項物件屬性x,所以obj.x是1,而非undefined。第13行又定義了原型方法b2,所以b原型也具有了b2。雖然第9~11行設定了原型方法b1,但是你會發現第12行執行後,b原型不再具有b1方法,也就是obj.b1是undefined。因為第12行使得b原型指向改變,原來具有b1的原型物件被拋棄,自然就沒有b1了。
第12行執行完後,b原型(b.prototype)指向了a的例項物件,而a的例項物件的構造器是建構函式a,所以b.prototype.constructor就是構造物件a了(換句話說,a構造了b的原型)。
alert(b.prototype.constructor)出來後就是"function a(x)" 。同樣地,obj.constructor也是a構造物件,alert(obj.constructor)出來後就是"function a(x)" ,也就是說b.prototype.constructor===obj.constructor(true),但是b.prototype===obj.constructor.prototype(false),因為前者是b的原型,具有成員:x,a,b2,後者是a的原型,具有成員:a。如何修正這個問題呢,就在第16行,將b原型的構造器重新指向了b建構函式,那麼b.prototype===obj.constructor.prototype(true),都具有成員:x,a,b2。
如果沒有第16行,那是不是obj = new b(1,3)會去呼叫a建構函式例項化呢?答案是否定的,你會發現obj.y=3,所以仍然是呼叫的b建構函式例項化的。雖然obj.constructor===a(true),但是對於new b()的行為來說,執行了上面所說的通過建構函式建立例項物件的3個步驟,第一步,建立空物件;第二步,obj.__proto__ === b.prototype,b.prototype是具有x,a,b2成員的,obj.constructor指向了b.prototype.constructor,即建構函式a;第三步,呼叫的建構函式b去設定和初始化成員,具有了屬性x,y。雖然不加16行不影響obj的屬性,但如上一段說,卻影響obj.constructor和obj.constructor.prototype。所以在使用了原型繼承後,要進行修正的操作。
關於第12、16行,總言之,第12行使得b原型繼承了a的原型物件的所有成員,但是也使得b的例項物件的構造器的原型指向了a原型,所以要通過第16行修正這個缺陷。
前端開發必須知道的JS(一) 原型和繼承
原型和閉包是js語言的難點,此文主要講原型及原型實現的繼承,在 二 中會講下閉包,希望對大家有所幫助。若有疑問或不正之處,歡迎提出指正和討論。一.原型與建構函式 js所有的函式都有乙個prototype屬性,這個屬性引用了乙個物件,即原型物件,也簡稱原型。這個函式包括建構函式和普通函式,我們講的更多...
前端開發必須知道的JS(一) 原型和繼承
原型和閉包是js語言的難點,此文主要講原型及原型實現的繼承,在 二 中會講下閉包,希望對大家有所幫助。若有疑問或不正之處,歡迎提出指正和討論。一.原型與建構函式 js所有的函式都有乙個prototype屬性,這個屬性引用了乙個物件,即原型物件,也簡稱原型。這個函式包括建構函式和普通函式,我們講的更多...
前端開發必須知道的JS(一) 原型和繼承
原型和閉包是js語言的難點,此文主要講原型及原型實現的繼承,在 二 中會講下閉包,希望對大家有所幫助。若有疑問或不正之處,歡迎提出指正和討論。一.原型與建構函式 js所有的函式都有乙個prototype屬性,這個屬性引用了乙個物件,即原型物件,也簡稱原型。這個函式包括建構函式和普通函式,我們講的更多...