天天看點

JavaScript中原型對象的徹底了解一、什麼是原型二、與原型有關的幾個屬性和方法三、組合原型模型和構造函數模型建立對象四、動态原型模式建立對象

在JavaScript中,我們建立一個函數A(就是聲明一個函數), 那麼浏覽器就會在記憶體中建立一個對象B,而且每個函數都預設會有一個屬性 prototype 指向了這個對象( 即:prototype的屬性的值是這個對象 )。這個對象B就是函數A的原型對象,簡稱函數的原型。這個原型對象B 預設會有一個屬性 constructor 指向了這個函數A ( 意思就是說:constructor屬性的值是函數A )。

看下面的代碼:

下面的圖描述了聲明一個函數之後發生的事情:

JavaScript中原型對象的徹底了解一、什麼是原型二、與原型有關的幾個屬性和方法三、組合原型模型和構造函數模型建立對象四、動态原型模式建立對象

當把一個函數作為構造函數 (理論上任何函數都可以作為構造函數) 使用new建立對象的時候,那麼這個對象就會存在一個預設的不可見的屬性,來指向了構造函數的原型對象。 這個不可見的屬性我們一般用 [[prototype]] 來表示,隻是這個屬性沒有辦法直接通路到。

觀察下面的示意圖:

JavaScript中原型對象的徹底了解一、什麼是原型二、與原型有關的幾個屬性和方法三、組合原型模型和構造函數模型建立對象四、動态原型模式建立對象

說明:

從上面的圖示中可以看到,建立p1對象雖然使用的是Person構造函數,但是對象建立出來之後,這個p1對象其實已經與Person構造函數沒有任何關系了,p1對象的[[prototype ]]屬性指向的是Person構造函數的原型對象。

如果使用new Person()建立多個對象,則多個對象都會同時指向Person構造函數的原型對象。

我們可以手動給這個原型對象添加屬性和方法,那麼p1,p2,p3…這些對象就會共享這些在原型中添加的屬性和方法。

如果我們通路p1中的一個屬性name,如果在p1對象中找到,則直接傳回。如果p1對象中沒有找到,則直接去p1對象的[[prototype]]屬性指向的原型對象中查找,如果查找到則傳回。(如果原型中也沒有找到,則繼續向上找原型的原型—原型鍊。後面再講)。

如果通過p1對象添加了一個屬性name,則p1對象來說就屏蔽了原型中的屬性name。換句話說:在p1中就沒有辦法通路到原型的屬性name了。

通過p1對象隻能讀取原型中的屬性name的值,而不能修改原型中的屬性name的值。 p1.name = “李四”; 并不是修改了原型中的值,而是在p1對象中給添加了一個屬性name。

JavaScript中原型對象的徹底了解一、什麼是原型二、與原型有關的幾個屬性和方法三、組合原型模型和構造函數模型建立對象四、動态原型模式建立對象

​prototype 存在于構造函數中 (其實任意函數中都有,隻是不是構造函數的時候prototype我們不關注而已) ,他指向了這個構造函數的原型對象。

​ 參考前面的示意圖。

​constructor屬性存在于原型對象中,他指向了構造函數。

用構造方法建立一個新的對象之後,這個對象中預設會有一個不可通路的屬性 [[prototype]] , 這個屬性就指向了構造方法的原型對象。

但是在個别浏覽器中,也提供了對這個屬性[[prototype]]的通路(chrome浏覽器和火狐浏覽器。ie浏覽器不支援)。通路方式:p1.proto

​但是開發者盡量不要用這種方式去通路,因為操作不慎會改變這個對象的繼承原型鍊。

​大家知道,我們用去通路一個對象的屬性的時候,這個屬性既有可能來自對象本身,也有可能來自這個對象的[[prototype]]屬性指向的原型。

​ 那麼如何判斷這個對象的來源呢?

​ hasOwnProperty方法,可以判斷一個屬性是否來自對象本身。

in操作符用來判斷一個屬性是否存在于這個對象中。但是在查找這個屬性時候,現在對象本身中找,如果對象找不到再去原型中找。換句話說,隻要對象和原型中有一個地方存在這個屬性,就傳回true

​ 原型中的所有的屬性都是共享的。也就是說,用同一個構造函數建立的對象去通路原型中的屬性的時候,大家都是通路的同一個對象,如果一個對象對原型的屬性進行了修改,則會反映到所有的對象上面。

​ 但是在實際使用中,每個對象的屬性一般是不同的。張三的姓名是張三,李四的姓名是李四。

​ ==但是,這個共享特性對 方法(屬性值是函數的屬性)又是非常合适的。==所有的對象共享方法是最佳狀态。這種特性在c#和Java中是天生存在的。

在構造函數中添加的屬性和方法,每個對象都有自己獨有的一份,大家不會共享。這個特性對屬性比較合适,但是對方法又不太合适。因為對所有對象來說,他們的方法應該是一份就夠了,沒有必要每人一份,造成記憶體的浪費和性能的低下。

可以使用下面的方法解決:

但是上面的這種解決方法具有緻命的缺陷:封裝性太差。使用面向對象,目的之一就是封裝代碼,這個時候為了性能又要把代碼抽出對象之外,這是反人類的設計。

原型模式适合封裝方法,構造函數模式适合封裝屬性,綜合兩種模式的優點就有了組合模式。

​前面講到的組合模式,也并非完美無缺,有一點也是感覺不是很完美。把構造方法和原型分開寫,總讓人感覺不舒服,應該想辦法把構造方法和原型封裝在一起,是以就有了動态原型模式。

​ 動态原型模式把所有的屬性和方法都封裝在構造方法中,而僅僅在需要的時候才去在構造方法中初始化原型,又保持了同時使用構造函數和原型的優點。

組合模式和動态原型模式是JavaScript中使用比較多的兩種建立對象的方式。

建議以後使用動态原型模式。他解決了組合模式的封裝不徹底的缺點。

繼續閱讀