原型和閉包是Js語言的難點,此文主要講原型及原型實現的繼承,在(二)中會講下閉包,但願對你們有所幫助。如有疑問或不正之處,歡迎提出指正和討論。閉包
一. 原型與構造函數app
Js全部的函數都有一個prototype屬性,這個屬性引用了一個對象,即原型對象,也簡稱原型。這個函數包括構造函數和普通函數,咱們講的更可能是構造函數的原型,可是也不可否定普通函數也有原型。譬如普通函數:函數
function F(){
;
}
alert(F.prototype instanceof Object) //truethis
構造函數,也即構造對象。首先了解下經過構造函數實例化對象的過程。spa
function A(x){
this.x=x;
}
var obj=new A(1);prototype
實例化obj對象有三步:code
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的原型對象的全部成員,固然,此時該原型對象是沒有成員的。
原型對象初始是空的,也就是沒有一個成員(即原型屬性和原型方法)。能夠經過以下方法驗證原型對象具備多少成員。
var num=0;
for(o in A.prototype) {
alert(o);//alert出原型屬性名字
num++;
}
alert("member: " + num);//alert出原型全部成員個數。
可是,一旦定義了原型屬性或原型方法,則全部經過該構造函數實例化出來的全部對象,都繼承了這些原型屬性和原型方法,這是經過內部的_proto_鏈來實現的。
譬如
A.prototype.say=function(){alert("Hi")};
那全部的A的對象都具備了say方法,這個原型對象的say方法是惟一的副本給你們共享的,而不是每個對象都有關於say方法的一個副本。
二. 原型與繼承
首先,看個簡單的繼承實現。
1 function A(x){
2 this.x=x;
3 }4 function B(x,y){
5 this.tmpObj=A;
6 this.tmpObj(x);
7 delete this.tmpObj;
8 this.y=y;
9 }
第五、六、7行:建立臨時屬性tmpObj引用構造函數A,而後在B內部執行,執行完後刪除。當在B內部執行了this.x=x後(這裏的this是B的對象),B固然就擁有了x屬性,固然B的x屬性和A的x屬性二者是獨立,因此並不能算嚴格的繼承。第五、六、7行有更簡單的實現,就是經過call(apply)方法:A.call(this,x);
這兩種方法都有將this傳遞到A的執行裏,this指向的是B的對象,這就是爲何不直接A(x)的緣由。這種繼承方式便是類繼承(js沒有類,這裏只是指構造函數),雖然繼承了A構造對象的全部屬性方法,可是不能繼承A的原型對象的成員。而要實現這個目的,就是在此基礎上再添加原型繼承。
經過下面的例子,就能很深刻地瞭解原型,以及原型參與實現的完美繼承。(本文核心在此^_^)
1 function A(x){
2 this.x = x;
3 }
4 A.prototype.a = "a";
5 function B(x,y){
6 this.y = y;
7 A.call(this,x);
8 }
9 B.prototype.b1 = function(){
10 alert("b1");
11 }
12 B.prototype = new A();
13 B.prototype.b2 = function(){
14 alert("b2");
15 }
16 B.prototype.constructor = B;
17 var obj = new B(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。因此在使用了原型繼承後,要進行修正的操做。
關於第十二、16行,總言之,第12行使得B原型繼承了A的原型對象的全部成員,可是也使得B的實例對象的構造器的原型指向了A原型,因此要經過第16行修正這個缺陷。