1. 程式人生 > >javascript原型物件、建構函式和例項物件

javascript原型物件、建構函式和例項物件

大家都知道,javascript中其實並沒有類的概念。但是,用建構函式跟原型物件卻可以模擬類的實現。在這裡,就先很不嚴謹的使用類這個詞,以方便說明。

下面整理了一些關於javascript的建構函式、原型物件以及例項物件的筆記,有錯誤的地方,望指正。

先用一張圖簡單的概括下這幾者之間的關係,再細化:


建構函式和例項物件

建構函式是類的外在表現,建構函式的名字通常用作類名。

其實建構函式也就是一個函式,只不過它於普通的函式又有點不同:

  • 沒有顯示的建立物件;
  • 直接將屬性和方法賦給this;
  • 沒有return語句;

建構函式是用來構造新物件的。之前的筆記中有提到過,可以是用new關鍵詞來呼叫建構函式,以建立特定型別的新物件。如,建立一個Object型別的物件例項:

var o=new Object();
為了區別建構函式和普通函式,通常規定建構函式的命名首字母大寫,而普通函式的命名首字母小寫。當然,這不是必須的,卻是一個很好的習慣。

通過用建構函式建立並初始化的屬性是例項屬性。所謂的例項屬性就是指,通過該建構函式建立的每個物件,都將擁有一份例項屬性的單獨拷貝。這些屬性都是通過例項來訪問的,值根據每個例項所定義的為準,若例項中沒有定義,則為建構函式初始化時的預設值。來看一個例子:

function Person(name,age){
   this.name=name;
   this.age=age;
   this.friends=["Tom","Boo"];
}

var p1=new Person("Lily",20);
var p2=new Person("Sam",30);
 
alert(p1.name);  //Lily
alert(p2.name);  //Sam
p1.friends.push("Susan");
alert(p1.friends);  //Tom,Boo,Susan
alert(p2.friends);  //Tom,Boo
上面的例子定義了一個Person建構函式,並初始化了name、age和friends三個屬性。接著建立了兩個例項物件,分別為p1和p2。觀察這個例子,每個屬性都是為各自所擁有的,並不會相互影響。這就是因為每個例項物件都擁有一份屬性的副本。
每個例項物件都有一個屬性指向它的建構函式,這屬性就是constructor:
function Person(name,age){
    this.name=name;
    this.age=age;
}

var p1=new Person("Lily",20);
var p2=new Person("Sam",30);
 
alert(p1.constructor==Person); //true
alert(p2.constructor==Person); //true
建構函式有一個prototype屬性,指向原型物件。

原型物件和例項物件

在javascript中,每個物件都有一個與之相關聯的物件,那就是它的原型物件。類的所有例項物件都從它的原型物件上繼承屬性。

原型物件是類的唯一標識:當且僅當兩個物件繼承自同一個原型物件時,它們才是屬於同一個類的例項。

前面有提到,建構函式擁有一個prototype屬性,指向原型。換句話來說,一個物件的原型就是它的建構函式的prototype屬性的值。當一個函式被定義的時候,它會自動建立和初始化prototype值,它是一個物件,這時這個物件只有一個屬性,那就是constructor,它指回和原型相關聯的那個建構函式。看個例子:

function Person(name,age){
   this.name=name;
   this.age=age;
}

alert(Person.prototype); //[object Object]
alert(Person.prototype.constructor==Person); //true
也可以通過原型來建立屬性和方法。通過原型建立的屬性和方法是被所有例項所共享的。即,在一個例項中修改了該屬性或方法的值,那麼所有其他例項的屬性或方法值都會受到影響:
function Person(name,age){
   this.name=name;
   this.age=age;
}

Person.prototype.friends=["Tom","Sam"];

var p1=new Person("Lily",24);
var p2=new Person("Susan",20);

alert(p1.friends); //Tom,Sam
alert(p2.friends); //Tom,Sam

p1.friends.push("Bill");
alert(p1.friends); //Tom,Sam,Bill
alert(p2.friends); //Tom,Sam,Bill
由上面的例子可以看出,用原型定義的屬性是被所有例項共享的。為p1添加了一個朋友,導致p2也添加了這個朋友。

其實,很多情況下,這種現象並不是我們想看到的。那麼什麼時候應該用建構函式初始化屬性和方法,哪些時候又該由原型物件來定義呢?

通常建議在建構函式內定義一般成員,即它的值在每個例項中都將不同,尤其是物件或陣列形式的值;而在原型物件中則定義一些所有例項所共享的屬性,即在所有例項中,它的值可以是相同的屬性。

當用建構函式建立一個例項時,例項的內部也包含了一個指標,指向建構函式的原型物件。一些瀏覽器中,支援一個屬性__proto__來表示這個內部指標:

function Person(name,age){
   this.name=name;
   this.age=age;
}

Person.prototype.sayName=function(){
   alert(this.name);
}

var p1=new Person("Lily",24);

alert(p1.__proto__.sayName); //function (){alert(this.name);}

alert(p1.__proto__.constructor==Person);  //true
在ECMAscript5中新增了一個方法,Object.getPrototypeOf(),可以返回前面提到的例項物件內部的指向其原型的指標的值:
function Person(name,age){
  this.name=name;
  this.age=age;
}

var p1=new Person("Lily",24);

alert(Object.getPrototypeOf(p1)==Person.prototype); //true
isPrototypeOf()方法也可用於確定例項物件和其原型之間的這種關係:
function Person(name,age){
   this.name=name;
   this.age=age;
}

var p1=new Person("Lily",24);

alert(Person.prototype.isPrototypeOf(p1)); //true

原型語法

從前面介紹原型物件於例項物件及建構函式的關係中,我們已經知道,給原型物件新增屬性和方法只要像這樣定義即可:Person.prototype=name。

那麼是否每定義一個Person的屬性,就要敲一遍Person.prototype呢?答案是否定的,我們也可以像用物件字面量建立物件那樣來建立原型物件:

function Person(){
}

Person.prototype={
   name:"Tom",
   age:29
}

var p1=new Person();

alert(p1.name); //Tom
alert(p1.age);  //29
有一點要注意,這個方法相當於重寫了整個原型物件,因此切斷了它與建構函式的關係,此時Person.prototype.constructor不再指向Person:
function Person(){
}

Person.prototype={
   name:"Tom",
   age:29
}

var p1=new Person();

alert(Person.prototype.constructor==Person); //false
alert(Person.prototype.constructor==Object); //true
因此,如果想要讓它重新指向Person,可以顯示的進行賦值:
function Person(){
}

Person.prototype={
  constructor:Person,
  name:"Tom",
  age:29
}

var p1=new Person();

alert(Person.prototype.constructor==Person); //true
alert(Person.prototype.constructor==Object); //false

總結

最後,我們拿一個例子,再來理理建構函式、原型物件以及例項物件之間的關係:

function Person(name,age){
           this.name=name;
           this.age=age;
       }

       Person.prototype.sayName=function(){
       	   alert(this.name);
       }


       var p1=new Person("Tom",20);

       alert(Person.prototype); //object
       alert(Person.prototype.constructor==Person); //true
       alert(p1.constructor==Person); //true
       alert(p1.__proto__==Person.prototype);  //true 
       alert(p1.__proto__.__proto__==Object.prototype); //true
       alert(p1.__proto__.__proto__.constructor==Object); //true
       alert(Person.constructor==Function); //true

       alert(Object.prototype.constructor==Object);


上圖說明了這個例子中原型、建構函式和例項屬性的關係。