天天看點

對象模型的細節對象模型的細節

<a target="_blank" href="https://developer.mozilla.org/zh-CN">MDN</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web">Web 技術文檔</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript">JavaScript</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide">JavaScript 指南</a>

對象模型的細節

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#toc">在本文章中</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#class-based_vs_prototype-based_languages">基于(類 vs 原型)的語言</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E5.AE.9A.E4.B9.89.E7.B1.BB">定義類</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E5.AD.90.E7.B1.BB.E5.92.8C.E7.BB.A7.E6.89.BF">子類和繼承</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E6.B7.BB.E5.8A.A0.E5.92.8C.E7.A7.BB.E9.99.A4.E5.B1.9E.E6.80.A7">添加和移除屬性</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E5.8C.BA.E5.88.AB.E6.91.98.E8.A6.81">差別摘要</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E9.9B.87.E5.91.98.E7.A4.BA.E4.BE.8B">雇員示例</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E5.88.9B.E5.BB.BA.E5.B1.82.E7.BA.A7.E7.BB.93.E6.9E.84">建立層級結構</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E5.AF.B9.E8.B1.A1.E7.9A.84.E5.B1.9E.E6.80.A7">對象的屬性</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E7.BB.A7.E6.89.BF.E5.B1.9E.E6.80.A7">繼承屬性</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E6.B7.BB.E5.8A.A0.E5.B1.9E.E6.80.A7">添加屬性</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#more_flexible_constructors">更靈活的構造器</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E5.86.8D.E8.B0.88.E5.B1.9E.E6.80.A7.E7.9A.84.E7.BB.A7.E6.89.BF">再談屬性的繼承</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E6.9C.AC.E5.9C.B0.E5.80.BC.E5.92.8C.E7.BB.A7.E6.89.BF.E5.80.BC">本地值和繼承值</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E5.88.A4.E6.96.AD.E5.AE.9E.E4.BE.8B.E7.9A.84.E5.85.B3.E7.B3.BB">判斷執行個體的關系</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E6.9E.84.E9.80.A0.E5.99.A8.E4.B8.AD.E7.9A.84.E5.85.A8.E5.B1.80.E4.BF.A1.E6.81.AF">構造器中的全局資訊</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#.E6.B2.A1.E6.9C.89.E5.A4.9A.E7.BB.A7.E6.89.BF">沒有多繼承</a>

JavaScript 是一種基于原型的面向對象語言,而不是基于類的。正是由于這一根本的差別,其如何建立對象的層級結構以及如何繼承屬性和屬性值,可能不是那麼清晰。本節将試着闡明這一問題。

本節假設您已經有點 JavaScript基礎,并且用 JavaScript 的函數建立過簡單的對象。

基于類的面向對象語言,比如 Java 和 C++,是建構在兩個不同實體的概念之上的:即類和執行個體。

類(class):定義了所有用于具有某一組特征對象的屬性(可以将 Java 中的方法和變量以及 C++ 中的成員都視作屬性)。類是抽象的事物,而不是其所描述的全部對象中的任何特定的個體。例如 <code>Employee</code> 類可以用來表示所有雇員的集合。

執行個體(instance):則是類的執行個體化展現;,或者說,是它的一個成員。例如, <code>Victoria</code> 可以是 <code>Employee</code> 類的一個執行個體,表示一個特定的雇員個體。執行個體具有和其父類完全一緻的屬性。

基于原型的語言(如 JavaScript)并不存在這種差別:它隻有對象。基于原型的語言具有所謂原型對象(prototypical object)的概念。原型對象可以作為一個模闆,新對象可以從中獲得原始的屬性。任何對象都可以指定其自身的屬性,既可以是建立時也可以在運作時建立。而且,任何對象都可以作為另一個對象的原型(prototype),進而允許後者共享前者的屬性。

在基于類的語言中,需要專門的類定義符(class definition)定義類。在定義類時,允許定義特殊的方法,稱為構造器(constructor),來建立該類的執行個體。在構造器方法中,可以指定執行個體的屬性的初始值以及一些其他的操作。您可以通過将<code>new</code> 操作符和構造器方法結合來建立類的執行個體。

JavaScript 也遵循類似的模型,但卻不使用構造器之外的類定義。隻需要定義構造器函數,用于建立具有一組特定的初始屬性和屬性值的對象。(JavaScript follows a similar model, but does not have a class definition separate from the constructor. Instead, you define a constructor function to create objects with a particular initial set of properties and values. )任何 JavaScript 函數都可以用作構造器。 <code>new</code> 操作符用來和構造器一起建立新對象。

基于類的語言是通過對類的定義中建構類的層級結構的。在類定義中,可以指定新的類是一個現存的類的子類。子類将繼承父類的全部屬性,并可以添加新的屬性或者修改繼承的屬性。例如,假設 <code>Employee</code> 類隻有 <code>name</code> 和 <code>dept</code> 屬性,而 <code>Manager</code> 是 <code>Employee</code> 的子類并添加了 <code>reports</code> 屬性。這時,<code>Manager</code> 類的執行個體将具有所有三個屬性:<code>name,</code><code>dept 和</code> <code>reports</code>。

JavaScript 通過允許在構造器函數中與原型對象相關聯的方式來實作繼承的。這樣,您可以建立完全一樣的 <code>Employee</code> — <code>Manager</code> 示例,不過需要使用略微不同的術語。首先,定義 <code>Employee</code> 構造器函數,指定 <code>name</code> 和 <code>dept</code> 屬性;然後,定義 <code>Manager</code> 構造器函數,指定 <code>reports</code> 屬性。最後,将一個新的 <code>Employee</code> 對象指派給 <code>Manager</code> 構造器函數的 <code>prototype</code> 屬性。這樣,當建立一個新的 <code>Manager</code> 對象時,它将從 <code>Employee</code> 對象中繼承 <code>name</code> and <code>dept</code> 屬性。

在基于類的語言中,通常在編譯時建立類,然後在編譯時或者運作時對類的執行個體進行執行個體化。一旦定義了類,無法對類的屬性進行更改。然而,在 JavaScript 中,允許運作時添加或者移除任何對象的屬性。如果您為一個對象中添加了一個屬性,而這個對象又作為其它對象的原型,則以該對象作為原型的所有其它對象也将獲得該屬性。

下面的表格摘要給出了上述差別。本節的後續部分将描述有關使用 JavaScript 構造器和原型建立對象層級結構的詳細資訊,并将其與在 Java 中的做法加以對比。

基于類的(Java) 基于原型的(JavaScript)

類和執行個體是不同的事物。 所有對象均為執行個體。

通過類定義來定義類;通過構造器方法來執行個體化類。 通過構造器函數來定義和建立一組對象。

通過 <code>new</code> 操作符建立單個對象。 相同。

通過類定義來定義現存類的子類,進而建構對象的層級結構。 指定一個對象作為原型并且與構造函數一起建構對象的層級結構

遵循類鍊繼承屬性。 遵循原型鍊繼承屬性。

類定義指定類的所有執行個體的所有屬性。無法在運作時動态添加屬性。 構造器函數或原型指定初始的屬性集。允許動态地向單個的對象或者整個對象集中添加或移除屬性。

本節的餘下部分将使用如下圖所示的雇員層級結構。

對象模型的細節對象模型的細節

圖8.1:一個簡單的對象層級

例子中會使用以下對象:

<code>Employee</code> 具有 <code>name</code> 屬性(預設值為空的字元串)和 <code>dept</code> 屬性(預設值為 "general")。

<code>Manager</code> 是 <code>Employee的子類。它添加了</code> <code>reports</code> 屬性(預設值為空的數組,以 <code>Employee</code> 對象數組作為它的值)。

<code>WorkerBee</code> 是 <code>Employee的子類</code>。它添加了 <code>projects</code> 屬性(預設值為空的數組,以字元串數組作為它的值)。

<code>SalesPerson</code> 是 <code>WorkerBee的子類</code>。它添加了 <code>quota</code> 屬性(其值預設為 100)。它還重載了 <code>dept</code> 屬性值為 "sales",表明所有的銷售人員都屬于同一部門。

<code>Engineer</code> 基于 <code>WorkerBee</code>。它添加了 <code>machine</code> 屬性(其值預設為空的字元串)同時重載了 <code>dept</code> 屬性值為 "engineering"。

可以有幾種不同的方式來定義适當的構造器函數,進而實作雇員的層級結構。如何選擇很大程度上取決于您希望在您的應用程式中能做到什麼。

本節介紹了如何使用非常簡單的(同時也是相當不靈活的)定義,使得繼承得以實作。在定義完成後,就無法在建立對象時指定屬性的值。新建立的對象僅僅獲得了預設值,當然允許随後加以修改。圖例 8.2 展現了這些簡單的定義形成的層級結構。

對象模型的細節對象模型的細節

圖 8.2:Employee 對象定義

下面關于 <code>Employee</code> 的 Java 和 JavaScript 的定義是非常類似的。唯一的不同是在 Java 中需要指定每個屬性的類型,而在 JavaScript 中則不需要,同時 Java 的類必須建立一個顯式的構造器方法。

JavaScript Java

<code>Manager</code> 和 <code>WorkerBee</code> 的定義表示在如何指定繼承鍊中上一層對象時,兩者存在不同點。在 JavaScript 中,您會添加一個原型執行個體作為構造器函數<code>prototype</code> 屬性的值,而這一動作可以在構造器函數定義後的任意時刻執行。而在 Java 中,則需要在類定義中指定父類,且不能在類定義之外改變父類。

<code>在對Engineer</code> 和 <code>SalesPerson</code> 定義時,建立了繼承自 <code>WorkerBee</code> 的對象,該對象會進而繼承自<code>Employee</code>。這些對象會具有在這個鍊之上的所有對象的屬性。另外,它們在定義時,又重載了繼承的 <code>dept</code> 屬性值,賦予新的屬性值。

在上述對象的定義完成後,您就可以建立這些對象的執行個體并擷取其屬性的預設值。圖8.3 表示了使用JavaScript 建立新對象的過程并顯示了新對象中的屬性值。

Note: 術語 執行個體(instance)在基于類的語言中具有特定的技術含義。在這些語言中,執行個體是指類的個體成員,與類有着根本性的不同。在 JavaScript 中,“執行個體”并不具有這種技術含義,因為 JavaScript 中不存在類和執行個體之間的這種差異。然而,在談論 JavaScript 時,“執行個體”可以非正式地用于表示用特定的構造器函數建立的對象。是以,在這個例子中,你可以非正式地稱<code>jane</code> 是 <code>Engineer</code> 的一個執行個體。與之類似,盡管術語父(parent),子(child),祖先(ancestor),和後代(descendant)在 JavaScript 中并沒有正式的含義,您可以非正式地使用這些術語用于指代原型鍊中處于更高層次或者更低層次的對象。

對象模型的細節對象模型的細節

圖例 8.3:通過簡單的定義建立對象

本節将讨論對象如何從原型鍊中的其它對象中繼承屬性,以及在運作時添加屬性的相關細節。

當 JavaScript 發現 <code>new</code> 操作符時,它會建立一個通用(generic)對象,并将其作為關鍵字 <code>this</code> 的值傳遞給 <code>WorkerBee</code> 的構造器函數。該構造器函數顯式地設定 <code>projects</code> 屬性的值,然後隐式地将其内部的 <code>__proto__</code> 屬性設定為 <code>WorkerBee.prototype</code> 的值(屬性的名稱前後均有兩個下劃線)。<code>__proto__</code> 屬性決定了用于傳回屬性值的原型鍊。一旦這些屬性設定完成,JavaScript 傳回新建立的對象,然後指派語句會将變量 <code>mark</code> 的值指向該對象。

這個過程不會顯式的将 <code>mark所繼承的原型鍊中的屬性值作為本地變量存放在</code> <code>mark</code> 對象中。當請求屬性的值時,JavaScript 将首先檢查對象自身中是否存在屬性的值,如果有,則傳回該值。如果不存在,JavaScript會通過 <code>__proto__對原型鍊進行檢查</code>。如果原型鍊中的某個對象包含該屬性的值,則傳回這個值。如果沒有找到該屬性,JavaScript 則認為對象中不存在該屬性。這樣,<code>mark</code> 對象中将具有如下的屬性和對應的值:

由于這些構造器不支援為執行個體設定特定的值,是以這些屬性值僅僅是建立自 <code>WorkerBee</code> 的所有對象所共享的預設值。當然這些屬性的值是可以修改的,是以您可以為 <code>mark指定特定的資訊</code>,如下所示:

在 JavaScript 中,您可以在運作時為任何對象添加屬性,而不必受限于構造器函數提供的屬性。添加特定于某個對象的屬性,隻需要為該對象指定一個屬性值,如下所示:

這樣 <code>mark</code> 對象就有了 <code>bonus</code> 屬性,而其它 <code>WorkerBee</code> 則沒有該屬性。

如果您向某個構造器函數的原型對象中添加新的屬性,那麼該屬性将添加到從這個原型中繼承屬性的所有對象的中。例如,可以通過如下的語句向所有雇員中添加 <code>specialty</code> 屬性:

隻要 JavaScript 執行了該語句,則 <code>mark</code> 對象也将具有 <code>specialty</code> 屬性,其值為 <code>"none"</code>。下圖則表示了在 <code>Employee</code> 原型中添加該屬性,然後在 <code>Engineer</code> 的原型中重載該屬性的效果。

對象模型的細節對象模型的細節

圖 8.4: 添加屬性

截至到現在,構造器函數都不允許在建立新的執行個體時指定屬性值。其實我們也可以像Java一樣,為構造器提供參數以初始化執行個體的屬性值。下圖即實作方式之一。

對象模型的細節對象模型的細節

Figure 8.5: Specifying properties in a constructor, take 1

下面的表格中羅列了這些對象在 Java 和 JavaScript 中的定義。

上面使用 JavaScript 定義過程使用了一種設定預設值的特殊慣用法:

JavaScript 的邏輯或操作符(<code>||)會對</code>第一個參數進行判斷。如果該參數值運算後結果為真,則操作符傳回該值。否則,操作符傳回第二個參數的值。是以,這行代碼首先檢查 <code>name</code> 是否是對<code>name</code> 屬性有效的值。如果是,則設定其為 <code>this.name</code> 的值。否則,設定 <code>this.name</code> 的值為空的字元串。盡管這種用法乍看起來有些費解,為了簡潔起見,本章将使用這種習慣用法。

注意:如果調用構造器函數時,指定了可以轉換為 <code><code>false</code></code> 的參數(比如 <code>0</code> (零)和空字元串<code><code>("")),結果可能出乎調用者意料。此時,将使用預設值(譯者注:而不是指定的參數值 0 和 "")。</code></code>

此時,<code>Jane</code> 的屬性如下所示:

注意,由上面對類的定義,您無法為諸如 <code>name</code> 這樣的繼承屬性指定初始值。如果想在JavaScript中為繼承的屬性指定初始值,您需要在構造器函數中添加更多的代碼。

到目前為止,構造器函數已經能夠建立一個普通對象,然後為新對象指定本地的屬性和屬性值。您還可以通過直接調用原型鍊上的更高層次對象的構造器函數,讓構造器添加更多的屬性。下圖即實作了這一功能。

對象模型的細節對象模型的細節

圖 8.6 Specifying properties in a constructor, take 2

下面是 <code>Engineer</code> 構造器的定義:

假設您建立了一個新的 <code>Engineer</code> 對象,如下所示:

執行時,JavaScript 會有以下步驟:

<code>new</code> 操作符建立了一個新的通用對象,并将其 <code>__proto__</code> 屬性設定為 <code>Engineer.prototype</code>。

<code>new</code> 操作符将該新對象作為 <code>this</code> 的值傳遞給 <code>Engineer</code> 構造器。

構造器為該新對象建立了一個名為 <code>base</code> 的新屬性,并指向 <code>WorkerBee</code> 的構造器。這使得 <code>WorkerBee</code> 構造器成為 <code>Engineer</code> 對象的一個方法。<code>base</code> 屬性的名稱并沒有什麼特殊性,我們可以使用任何其他合法的名稱來代替;<code>base</code> 僅僅是為了貼近它的用意。

構造器調用 <code>base</code> 方法,将傳遞給該構造器的參數中的兩個,作為參數傳遞給 <code>base</code> 方法,同時還傳遞一個字元串參數  <code>"engineering"。顯式地在構造器中使用</code> <code>"engineering"</code> 表明所有 <code>Engineer</code> 對象繼承的 <code>dept</code> 屬性具有相同的值,且該值重載了繼承自 <code>Employee</code> 的值。

因為 <code>base</code> 是 <code>Engineer</code> 的一個方法,在調用 <code>base</code> 時,JavaScript 将在步驟 1 中建立的對象綁定給 <code>this</code> 關鍵字。這樣,<code>WorkerBee</code> 函數接着将 <code>"Doe, Jane"</code> 和 <code>"engineering"</code> 參數傳遞給 <code>Employee</code> 構造器函數。當從 <code>Employee</code> 構造器函數傳回時,<code>WorkerBee</code> 函數用剩下的參數設定 <code>projects</code> 屬性。

當從 <code>base</code> 方法傳回時,<code>Engineer</code> 構造器将對象的 <code>machine</code> 屬性初始化為 <code>"belau"</code>。

當從構造器傳回時,JavaScript 将新對象指派給 <code>jane</code> 變量。

您可以認為,在 <code>Engineer</code> 的構造器中調用了 <code>WorkerBee</code> 的構造器,也就為 <code>Engineer</code> 對象設定好了繼承關系。事實并非如此。調用 <code>WorkerBee</code> 構造器確定了<code>Engineer</code> 對象以所有在構造器中所指定的屬性被調用。但是,如果後續在 <code>Employee</code> 或者 <code>WorkerBee</code> 原型中添加了屬性,那些屬性不會被 <code>Engineer</code> 對象繼承。例如,假設如下語句:

對象 <code>jane</code> 不會繼承 <code>specialty</code> 屬性。您必須顯式地設定原型才能確定動态的繼承。如果修改成如下的語句:

現在 <code>jane</code> 對象的 <code>specialty</code> 屬性為 "none" 了。

使用 javascript 的 <code>call()</code> 方法相對明了一些,因為無需 <code>base</code> 方法了。

前面的小節中描述了 JavaScript 構造器和原型如何提供層級結構和繼承的實作。本節中對之前未讨論的一些細節進行闡述。

正如本章前面所述,在通路一個對象的屬性時,JavaScript 将執行下面的步驟:

檢查本地值是否存在。如果存在,傳回該值。

如果本地值不存在,檢查原型鍊(通過 <code>__proto__</code> 屬性)。

如果原型鍊中的某個對象具有指定屬性的值,則傳回該值。

如果這樣的屬性不存在,則對象沒有該屬性。

以上步驟的結果依賴于您是如何定義的。最早的例子中具有如下定義:

基于這些定義,假定通過如下的語句建立 <code>WorkerBee</code> 的執行個體 <code>amy:</code>

則 <code>amy</code> 對象将具有一個本地屬性,<code>projects。</code><code>name</code> 和 <code>dept</code> 屬性則不是 <code>amy</code> 對象本地的,而是從 <code>amy</code> 對象的 <code>__proto__</code> 屬性獲得的。是以,<code>amy</code> 将具有如下的屬性值:

現在,假設修改了與 <code>Employee</code> 的相關聯原型中的 <code>name</code> 屬性的值:

乍一看,您可能覺得新的值會傳播給所有 <code>Employee</code> 的執行個體。然而,并非如此。

在建立 <code>Employee</code> 對象的任意執行個體時,該執行個體的 <code>name</code> 屬性将獲得一個本地值(空的字元串)。這就意味着在建立一個新的 <code>Employee</code> 對象作為 <code>WorkerBee</code> 的原型時,<code>WorkerBee.prototype</code> 的 <code>name</code> 屬性将具有一個本地值。是以,當 JavaScript 查找 <code>amy</code> 對象(<code>WorkerBee</code> 的執行個體)的 <code>name</code> 屬性時,JavaScript 将找到 <code>WorkerBee.prototype</code> 中的本地值。是以,也就不會繼續在原型鍊中向上找到 <code>Employee.prototype</code> 了。

如果想在運作時修改一個對象的屬性值并且希望該值被所有該對象的後代所繼承,您就不能在該對象的構造器函數中定義該屬性。而應該将該屬性添加到該對象所關聯的原型中。例如,假設将前面的代碼作如下修改:

在這種情況下,<code>amy</code> 的 <code>name</code> 屬性将為 "Unknown"。

正如這些例子所示,如果希望對象的屬性具有預設值,并且希望在運作時修改這些預設值,應該在對象的原型中設定這些屬性,而不是在構造器函數中。

JavaScript 的屬性查找機制首先在對象自身的屬性中查找,如果指定的屬性名稱沒有找到,将在對象的特殊屬性 <code>__proto__</code> 中查找。這個過程是遞歸的;被稱為“在原型鍊中查找”。

特殊的 <code>__proto__</code> 屬性是在建構對象時設定的;設定為構造器的 <code>prototype</code> 屬性的值。是以表達式 <code>new Foo()</code> 将建立一個對象,其 <code>__proto__ == <code>Foo.prototype</code></code>。因而,修改 <code>Foo.prototype</code> 的屬性,将改變所有通過 <code>new Foo()</code> 建立的對象的屬性的查找。

每個對象都有一個 <code>__proto__</code> 對象屬性(除了 <code>Object);每個函數都有一個</code> <code>prototype</code> 對象屬性。是以,通過“原型繼承(prototype inheritance)”,對象與其它對象之間形成關系。通過比較對象的 <code>__proto__</code> 屬性和函數的 <code>prototype</code> 屬性可以檢測對象的繼承關系。JavaScript 提供了便捷方法:<code>instanceof</code> 操作符可以用來将一個對象和一個函數做檢測,如果對象繼承自函數的原型,則該操作符傳回真。例如:

對于該對象,以下所有語句均為真:

基于此,可以寫出一個如下所示的 <code>instanceOf</code> 函數:

但如下表達式為假:

在建立構造器時,在構造器中設定全局資訊要小心。例如,假設希望為每一個雇員配置設定一個唯一辨別。可能會為 <code>Employee</code> 使用如下定義:

基于該定義,在建立新的 <code>Employee</code> 時,構造器為其配置設定了序列中的下一個辨別符。然後遞增全局的辨別符計數器。是以,如果,如果随後的語句如下,則 <code>victoria.id</code> 為 1 而 <code>harry.id</code> 為 2:

乍一看似乎沒問題。但是,無論什麼目的,在每一次建立 <code>Employee</code> 對象時,<code>idCounter</code> 都将被遞增一次。如果建立本章中所描述的整個 <code>Employee</code> 層級結構,每次設定原型的時候,<code>Employee</code> 構造器都将被調用一次。假設有如下代碼:

還可以進一步假設上面省略掉的定義中包含 <code>base</code> 屬性而且調用了原型鍊中高于它們的構造器。即便在現在這個情況下,在 <code>mac</code> 對象建立時,<code>mac.id</code> 為 5。

依賴于應用程式,計數器額外的遞增可能有問題,也可能沒問題。如果确實需要準确的計數器,則以下構造器可以作為一個可行的方案:

在用作原型而建立新的 <code>Employee</code> 執行個體時,不會指定參數。使用這個構造器定義,如果不指定參數,構造器不會指定辨別符,也不會遞增計數器。而如果想讓 <code>Employee</code> 配置設定到辨別符,則必需為雇員指定姓名。在這個例子中,<code>mac.id</code> 将為 1。

某些面向對象語言支援多重繼承。也就是說,對象可以從無關的多個父對象中繼承屬性和屬性值。JavaScript 不支援多重繼承。

JavaScript 屬性值的繼承是在運作時通過檢索對象的原型鍊來實作的。因為對象隻有一個原型與之關聯,是以 JavaScript 無法動态地從多個原型鍊中繼承。

在 JavaScript 中,可以在構造器函數中調用多個其它的構造器函數。這一點造成了多重繼承的假象。例如,考慮如下語句:

進一步假設使用本章前面所屬的 <code>WorkerBee</code> 的定義。此時 <code>dennis</code> 對象具有如下屬性:

<code>dennis</code> 确實從 <code>Hobbyist</code> 構造器中獲得了 <code>hobby</code> 屬性。但是,假設添加了一個屬性到 <code>Hobbyist</code> 構造器的原型:

<code>dennis</code> 對象不會繼承這個新屬性。

标簽: 

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/tag/prototype">prototype</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/tag/on-progress">on-progress</a>

<a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/tag/ObjectModel">ObjectModel</a>

© 2005-2015 Mozilla 開發者網絡和各貢獻者