1. 程式人生 > >JavaScript之建立物件的模式

JavaScript之建立物件的模式

使用Object的建構函式可以建立物件或者使用物件字面量來建立單個物件,但是這些方法有一個明顯的缺點:使用相同的一個介面建立很多物件,會產生大量的重複程式碼。
(一)工廠模式
這種模式抽象了建立具體物件的過程。考慮到在ECMAScript中無法建立類,開發人員就開發了一種函式,用函式來封裝以特定介面建立物件的細節:

工廠模式雖然解決了建立多個相似物件的問題,但是卻沒有解決物件識別的問題(即怎樣知道一個物件的型別)。

(二)建構函式模式

ECMAScript中的建構函式可以用來建立特定型別的物件。像Object和Array這樣的原生建構函式,在執行時會自動出現在執行環境中。此外,也可以建立自定義的建構函式,從而定義自定義物件型別的屬性和方法。例如,使用建構函式的方法重寫上訴問題:

呼叫建構函式實際是經歷如下的過程:
(1)建立一個物件;
(2)將建構函式的作用域賦給新物件(因此this就指向了這個新物件);
(3)執行建構函式中的程式碼(為新物件新增屬性);
(4)返回新物件;

使用這種模式,person1和person2物件分別儲存著Person的一個不同的例項。詞彙量測試這兩個物件都有一個constructor(建構函式)屬性,該屬性指向Person:
(函式名實際是個指標)

constructor屬性最初是用來標識物件型別的。以上建立的兩個物件即是Object的例項,同時也是Person(可以 person1 instanceof Person 來進行判斷)的例項(因為所有物件都繼承自Object),也就是說使用建構函式模式可以將它的例項標識為一種特定的型別,這也是建構函式模式勝過工廠模式的地方。


建構函式與其他函式的唯一區別就是在於它們的呼叫方式不同。任何函式,只要通過new操作符來呼叫,那它就可以作為建構函式。同理,前面的Person函式可以通過以下任意一種方式呼叫:

不使用new操作符的呼叫,屬性和方法都被新增給window物件。
有時建構函式可以這樣定義:

從這個角度看,每個Person例項都包含了一個不同的Function例項(以顯示name屬性)的本質。

然而建立兩個完成同樣任務的Function例項沒有必要,因此可以將函式定義轉移到建構函式外:

因此person1和person2物件就共享了在全域性作用域中定義的一個sayName()函式。英文地址但是全域性作用域中定義的函式只能被某個物件呼叫,並且如果物件需要定義很多方法,那麼就需要多個全域性變數函式,即沒有了封裝性

(三)原型模式
我們建立的每個函式都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個物件,它的用途是包含可以有特定型別的所有例項共享的屬性和方法。

prototype就是通過建構函式而建立的那個物件的原型物件。使用原型的好處就是可以讓所有物件例項共享它所包含的屬性和方法 。

(1)理解原型物件

無論什麼時候,只要建立了一個新函式,ECMAScript就會根據一組特定的規則為該函式建立一個prototype屬性,這個屬性指向函式的原型物件。在預設情況下,所有原型物件都會自動獲得一個constructor(建構函式)屬性,詞彙教學這個屬性包含一個指向prototype屬性所在函式的指標。就拿前面的例子,Person.prototype.constructor指向Person。而通過這個建構函式,我們還可以繼續為原型物件新增其他屬性和方法。
建立了自定義指標之後,其原型物件預設只會取得constructor屬性;至於其它方法,都會從Object物件繼承而來。當呼叫建構函式建立一個新例項之後,該例項的內部將包括一個指標(內部屬性),指向建構函式的原型物件。ECMA-262第5版中管這個叫[[Prototype]]。
要明確一點的就是,這個連線存在於例項和建構函式的原型物件之間,而不是存在於例項和建構函式之間。
以前面使用的Person建構函式和Person.prototype建立例項的程式碼為例,如下圖:

這裡寫圖片描述

上圖展示了Person建構函式、Person的原型屬性以及Person現有的兩個例項之間的關係。在此,Person.prototype指向了原型物件,而Person.prototype.constructor又指回了Person。原型物件中除了包含constructor屬性之外,香港大學學費還包括後來新增的其他屬性。Person的每一個例項——person1和person2都包含一個內部屬性,該屬性僅僅指向Person.prototype。換句話說,它們與建構函式沒有直接的聯絡。此外,要格外注意的是,雖然這兩個例項都不包含屬性和方法,但我們卻可以呼叫person1.sayName()。這是通過查詢物件屬性的過程來實現的。
雖然我們無法訪問到[[Prototype]],但可以通過isPrototypeOf()方法來確定物件之間是否存在這種關係。從本質上講,如果[[Prototype]]指向呼叫isPrototypeOf()方法的物件(Person.prototype),那麼這個方法就會返回true。

每當程式碼要讀取某個物件的屬性時,都會進行一次搜尋,搜尋目標是具有給定名稱的屬性。搜尋當然先從物件例項的本身開始,如果找到了,就可以返回該值了;如果找不到,則會去指標所指向的原型物件中去查詢,託福和雅思在原型物件中找到了,就可以順利返回該值。而這正是多個物件例項共享原型所儲存的屬性和方法的基本原理。
雖然可以通過物件例項訪問到儲存在原型中的值,但不能通過物件例項重寫原型中的值。根據查詢原理,如果找到了例項中的值,就不會再去查詢原型物件中的值。,程式碼如下所示:

使用hasOwnPeoperty()方法可以檢測一個屬性是否存在於例項中,還是存在原型中,這個方法(它是從Object繼承來的)只在給定屬性存在域物件例項中時,才返回true。

(2)原型與in操作符

有兩種方式使用in操作符:一、單獨使用;二、for-in中使用。
功能:會在通過物件能夠訪問給定屬性時返回true,無論是在物件例項中或是原型中。

同時使用hasOwnProperty()和in操作符可以判斷出該屬性到底是存在物件例項中還是存在與原型中。
使用for-in迴圈時,返回的是所有能夠通過物件訪問的,可列舉(enumerated)屬性,其中即包括存在與例項中的屬性,也包括存在與原型中的屬性。根據規定,開發人員定義的屬性都是可列舉的——IE8及更早版本除。

(3)更簡單的原型語法

結果是與先前的相同,但有一個是不同的:contrcutor屬性不再指向Person了。我們曾經介紹過,雅思寫作教材沒建立一個函式,就會同時建立它的prototype物件,這個物件也會自動獲得constructor屬性。而我們這樣寫,本質上是完全重寫了預設的prototype物件,因此constructor屬性也就變成了新物件的constructor屬性(指向Object建構函式),不再指向Person函式。
當然我可以將它特意設定成適當的值:

以上程式碼特意包含了一個constructor屬性,並將它的值設定為Person,從而確保了通過該屬效能夠訪問到適當的值。
(4)原型的動態性

由於在原型中查詢值的過程是一次搜尋,因此我們對原型物件所做的任何修改都能夠立即從例項上反映出來——即使是先建立了例項後修改原型也照樣可以,如下所示:

儘管可以隨時為原型新增屬性和方法,但如果我們重寫了整個原型物件,英語考級那麼情況就不一樣了。我們知道,呼叫建構函式時會為例項新增一個指向最初原型的[[Prototype]]指標,而把原型修改為另一個物件就等於切斷了建構函式與最初原型之間的聯絡。一定要記住:例項中的指標僅僅指向原型,而不是建構函式。如下例子:

這裡寫圖片描述

(6)原型物件的問題

原型模式的最大問題是由其共享的本性所導致的。原型中的所有屬性都被很多例項共享的,這種共享對於函式非常合適。對於那些包含基本值的屬性,通過例項上新增一個同名屬性,可以隱藏原型中的對應屬性。然而,對於包含引用型別值的屬性來書,問題便比較突出:

當一個物件想獲取獨有的操作時,原型模式的共享就是最大的阻礙。因此,目前使用最廣泛的是建構函式和原型混成的模式,建構函式模式用於定義例項屬性,而原型模式用於定於方法和共享屬性。

詳細可以參考《Professional JavaScript for Web Developers》3rd Edition