对象模型的细节
對象模型的細節
在本文章中JavaScript 是一種基于原型的面向對象語言,而不是基于類的。正是由于這一根本的區別,其如何創建對象的層級結構以及如何繼承屬性和屬性值,可能不是那么清晰。本節將試著闡明這一問題。
本節假設您已經有點 JavaScript基礎,并且用 JavaScript 的函數創建過簡單的對象。
基于(類 vs 原型)的語言
基于類的面向對象語言,比如 Java 和 C++,是構建在兩個不同實體的概念之上的:即類和實例。
- 類(class):定義了所有用于具有某一組特征對象的屬性(可以將 Java 中的方法和變量以及 C++ 中的成員都視作屬性)。類是抽象的事物,而不是其所描述的全部對象中的任何特定的個體。例如 Employee 類可以用來表示所有雇員的集合。
- 實例(instance):則是類的實例化體現;,或者說,是它的一個成員。例如, Victoria 可以是 Employee 類的一個實例,表示一個特定的雇員個體。實例具有和其父類完全一致的屬性。
基于原型的語言(如 JavaScript)并不存在這種區別:它只有對象。基于原型的語言具有所謂原型對象(prototypical object)的概念。原型對象可以作為一個模板,新對象可以從中獲得原始的屬性。任何對象都可以指定其自身的屬性,既可以是創建時也可以在運行時創建。而且,任何對象都可以作為另一個對象的原型(prototype),從而允許后者共享前者的屬性。
定義類
在基于類的語言中,需要專門的類定義符(class definition)定義類。在定義類時,允許定義特殊的方法,稱為構造器(constructor),來創建該類的實例。在構造器方法中,可以指定實例的屬性的初始值以及一些其他的操作。您可以通過將new 操作符和構造器方法結合來創建類的實例。
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 函數都可以用作構造器。 new 操作符用來和構造器一起創建新對象。
子類和繼承
基于類的語言是通過對類的定義中構建類的層級結構的。在類定義中,可以指定新的類是一個現存的類的子類。子類將繼承父類的全部屬性,并可以添加新的屬性或者修改繼承的屬性。例如,假設 Employee 類只有 name 和 dept 屬性,而 Manager 是 Employee 的子類并添加了 reports 屬性。這時,Manager 類的實例將具有所有三個屬性:name,dept 和 reports。
JavaScript 通過允許在構造器函數中與原型對象相關聯的方式來實現繼承的。這樣,您可以創建完全一樣的 Employee — Manager 示例,不過需要使用略微不同的術語。首先,定義 Employee 構造器函數,指定 name 和 dept 屬性;然后,定義 Manager 構造器函數,指定 reports 屬性。最后,將一個新的 Employee 對象賦值給 Manager 構造器函數的 prototype 屬性。這樣,當創建一個新的 Manager 對象時,它將從 Employee 對象中繼承 name and dept 屬性。
添加和移除屬性
在基于類的語言中,通常在編譯時創建類,然后在編譯時或者運行時對類的實例進行實例化。一旦定義了類,無法對類的屬性進行更改。然而,在 JavaScript 中,允許運行時添加或者移除任何對象的屬性。如果您為一個對象中添加了一個屬性,而這個對象又作為其它對象的原型,則以該對象作為原型的所有其它對象也將獲得該屬性。
區別摘要
下面的表格摘要給出了上述區別。本節的后續部分將描述有關使用 JavaScript 構造器和原型創建對象層級結構的詳細信息,并將其與在 Java 中的做法加以對比。
| 類和實例是不同的事物。 | 所有對象均為實例。 |
| 通過類定義來定義類;通過構造器方法來實例化類。 | 通過構造器函數來定義和創建一組對象。 |
| 通過 new 操作符創建單個對象。 | 相同。 |
| 通過類定義來定義現存類的子類,從而構建對象的層級結構。 | 指定一個對象作為原型并且與構造函數一起構建對象的層級結構 |
| 遵循類鏈繼承屬性。 | 遵循原型鏈繼承屬性。 |
| 類定義指定類的所有實例的所有屬性。無法在運行時動態添加屬性。 | 構造器函數或原型指定初始的屬性集。允許動態地向單個的對象或者整個對象集中添加或移除屬性。 |
雇員示例
本節的余下部分將使用如下圖所示的雇員層級結構。
圖8.1:一個簡單的對象層級
例子中會使用以下對象:
- Employee 具有 name 屬性(默認值為空的字符串)和 dept 屬性(默認值為 "general")。
- Manager?是?Employee的子類。它添加了 reports 屬性(默認值為空的數組,以 Employee 對象數組作為它的值)。
- WorkerBee?是?Employee的子類。它添加了 projects 屬性(默認值為空的數組,以字符串數組作為它的值)。
- SalesPerson?是?WorkerBee的子類。它添加了 quota 屬性(其值默認為 100)。它還重載了 dept 屬性值為 "sales",表明所有的銷售人員都屬于同一部門。
- Engineer 基于 WorkerBee。它添加了 machine 屬性(其值默認為空的字符串)同時重載了 dept 屬性值為 "engineering"。
創建層級結構
可以有幾種不同的方式來定義適當的構造器函數,從而實現雇員的層級結構。如何選擇很大程度上取決于您希望在您的應用程序中能做到什么。
本節介紹了如何使用非常簡單的(同時也是相當不靈活的)定義,使得繼承得以實現。在定義完成后,就無法在創建對象時指定屬性的值。新創建的對象僅僅獲得了默認值,當然允許隨后加以修改。圖例 8.2 展現了這些簡單的定義形成的層級結構。
在實際應用程序中,您很可能想定義構造器,以允許您在創建對象時指定屬性值。(參見 更靈活的構造器 獲得進一步的信息)。當前,這些簡單的定義只是說明了繼承是如何實現的。
圖 8.2:Employee 對象定義
下面關于?Employee 的 Java 和 JavaScript 的定義是非常類似的。唯一的不同是在 Java 中需要指定每個屬性的類型,而在 JavaScript 中則不需要,同時 Java 的類必須創建一個顯式的構造器方法。
| function Employee () {this.name = "";this.dept = "general"; } | public class Employee {public String name;public String dept;public Employee () {this.name = "";this.dept = "general";} } |
Manager 和 WorkerBee 的定義表示在如何指定繼承鏈中上一層對象時,兩者存在不同點。在 JavaScript 中,您會添加一個原型實例作為構造器函數prototype 屬性的值,而這一動作可以在構造器函數定義后的任意時刻執行。而在 Java 中,則需要在類定義中指定父類,且不能在類定義之外改變父類。
| function Manager () {this.reports = []; } Manager.prototype = new Employee;function WorkerBee () {this.projects = []; } WorkerBee.prototype = new Employee; | public class Manager extends Employee {public Employee[] reports;public Manager () {this.reports = new Employee[0];} }public class WorkerBee extends Employee {public String[] projects;public WorkerBee () {this.projects = new String[0];} } |
在對Engineer 和 SalesPerson?定義時,創建了繼承自 WorkerBee?的對象,該對象會進而繼承自Employee。這些對象會具有在這個鏈之上的所有對象的屬性。另外,它們在定義時,又重載了繼承的 dept 屬性值,賦予新的屬性值。
| function SalesPerson () {this.dept = "sales";this.quota = 100; } SalesPerson.prototype = new WorkerBee;function Engineer () {this.dept = "engineering";this.machine = ""; } Engineer.prototype = new WorkerBee; | public class SalesPerson extends WorkerBee {public double quota;public SalesPerson () {this.dept = "sales";this.quota = 100.0;} }public class Engineer extends WorkerBee {public String machine;public Engineer () {this.dept = "engineering";this.machine = "";} } |
在上述對象的定義完成后,您就可以創建這些對象的實例并獲取其屬性的默認值。圖8.3 表示了使用JavaScript 創建新對象的過程并顯示了新對象中的屬性值。
Note:?術語 實例(instance)在基于類的語言中具有特定的技術含義。在這些語言中,實例是指類的個體成員,與類有著根本性的不同。在 JavaScript 中,“實例”并不具有這種技術含義,因為 JavaScript 中不存在類和實例之間的這種差異。然而,在談論 JavaScript 時,“實例”可以非正式地用于表示用特定的構造器函數創建的對象。所以,在這個例子中,你可以非正式地稱jane 是 Engineer 的一個實例。與之類似,盡管術語父(parent),子(child),祖先(ancestor),和后代(descendant)在 JavaScript 中并沒有正式的含義,您可以非正式地使用這些術語用于指代原型鏈中處于更高層次或者更低層次的對象。
圖例 8.3:通過簡單的定義創建對象
對象的屬性
本節將討論對象如何從原型鏈中的其它對象中繼承屬性,以及在運行時添加屬性的相關細節。
繼承屬性
假設您通過如下語句創建一個 mark 對象作為 WorkerBee 的實例(如 圖例 8.3 所示):
var mark = new WorkerBee;當 JavaScript 發現 new 操作符時,它會創建一個通用(generic)對象,并將其作為關鍵字 this 的值傳遞給 WorkerBee 的構造器函數。該構造器函數顯式地設置 projects 屬性的值,然后隱式地將其內部的 __proto__ 屬性設置為 WorkerBee.prototype 的值(屬性的名稱前后均有兩個下劃線)。__proto__ 屬性決定了用于返回屬性值的原型鏈。一旦這些屬性設置完成,JavaScript 返回新創建的對象,然后賦值語句會將變量 mark 的值指向該對象。
這個過程不會顯式的將?mark所繼承的原型鏈中的屬性值作為本地變量存放在?mark 對象中。當請求屬性的值時,JavaScript 將首先檢查對象自身中是否存在屬性的值,如果有,則返回該值。如果不存在,JavaScript會通過?__proto__對原型鏈進行檢查。如果原型鏈中的某個對象包含該屬性的值,則返回這個值。如果沒有找到該屬性,JavaScript 則認為對象中不存在該屬性。這樣,mark 對象中將具有如下的屬性和對應的值:
mark.name = ""; mark.dept = "general"; mark.projects = [];mark 對象從 mark.__proto__ 中保存的原型對象中繼承了 name 和 dept 屬性的值。并由 WorkerBee 構造器函數為 projects 屬性設置了本地值。 這就是 JavaScript 中的屬性和屬性值的繼承。這個過程的一些微妙之處將在 再談屬性繼承 中進一步討論。
由于這些構造器不支持為實例設置特定的值,所以這些屬性值僅僅是創建自 WorkerBee 的所有對象所共享的默認值。當然這些屬性的值是可以修改的,所以您可以為 mark指定特定的信息,如下所示:
mark.name = "Doe, Mark"; mark.dept = "admin"; mark.projects = ["navigator"];添加屬性
在 JavaScript 中,您可以在運行時為任何對象添加屬性,而不必受限于構造器函數提供的屬性。添加特定于某個對象的屬性,只需要為該對象指定一個屬性值,如下所示:
mark.bonus = 3000;這樣 mark 對象就有了 bonus 屬性,而其它 WorkerBee 則沒有該屬性。
如果您向某個構造器函數的原型對象中添加新的屬性,那么該屬性將添加到從這個原型中繼承屬性的所有對象的中。例如,可以通過如下的語句向所有雇員中添加 specialty 屬性:
Employee.prototype.specialty = "none";只要 JavaScript 執行了該語句,則 mark 對象也將具有 specialty 屬性,其值為 "none"。下圖則表示了在 Employee 原型中添加該屬性,然后在 Engineer 的原型中重載該屬性的效果。
圖 8.4: 添加屬性
更靈活的構造器
截至到現在,構造器函數都不允許在創建新的實例時指定屬性值。其實我們也可以像Java一樣,為構造器提供參數以初始化實例的屬性值。下圖即實現方式之一。
Figure 8.5: Specifying properties in a constructor, take 1
下面的表格中羅列了這些對象在 Java 和 JavaScript 中的定義。
| function Employee (name, dept) {this.name = name || "";this.dept = dept || "general"; } | public class Employee {public String name;public String dept;public Employee () {this("", "general");}public Employee (String name) {this(name, "general");}public Employee (String name, String dept) {this.name = name;this.dept = dept;} } |
| function WorkerBee (projs) {this.projects = projs || []; } WorkerBee.prototype = new Employee; | public class WorkerBee extends Employee {public String[] projects;public WorkerBee () {this(new String[0]);}public WorkerBee (String[] projs) {projects = projs;} } |
| function Engineer (mach) {this.dept = "engineering";this.machine = mach || ""; } Engineer.prototype = new WorkerBee; | public class Engineer extends WorkerBee {public String machine;public Engineer () {dept = "engineering";machine = "";}public Engineer (String mach) {dept = "engineering";machine = mach;} } |
上面使用 JavaScript 定義過程使用了一種設置默認值的特殊慣用法:
this.name = name || "";JavaScript 的邏輯或操作符(||)會對第一個參數進行判斷。如果該參數值運算后結果為真,則操作符返回該值。否則,操作符返回第二個參數的值。因此,這行代碼首先檢查 name 是否是對name 屬性有效的值。如果是,則設置其為 this.name 的值。否則,設置 this.name 的值為空的字符串。盡管這種用法乍看起來有些費解,為了簡潔起見,本章將使用這種習慣用法。
注意:如果調用構造器函數時,指定了可以轉換為 false 的參數(比如 0 (零)和空字符串("")),結果可能出乎調用者意料。此時,將使用默認值(譯者注:而不是指定的參數值 0 和 "")。
由上面的定義,當創建對象的實例時,您可以為本地定義的屬性指定值。正如 圖例 8.5 所示一樣,您可以通過如下語句創建新的 Engineer:
var jane = new Engineer("belau");此時,Jane 的屬性如下所示:
jane.name == ""; jane.dept == "engineering"; jane.projects == []; jane.machine == "belau"注意,由上面對類的定義,您無法為諸如 name 這樣的繼承屬性指定初始值。如果想在JavaScript中為繼承的屬性指定初始值,您需要在構造器函數中添加更多的代碼。
到目前為止,構造器函數已經能夠創建一個普通對象,然后為新對象指定本地的屬性和屬性值。您還可以通過直接調用原型鏈上的更高層次對象的構造器函數,讓構造器添加更多的屬性。下圖即實現了這一功能。
圖 8.6 Specifying properties in a constructor, take 2
下面是 Engineer 構造器的定義:
function Engineer (name, projs, mach) {this.base = WorkerBee;this.base(name, "engineering", projs);this.machine = mach || ""; }假設您創建了一個新的 Engineer 對象,如下所示:
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");執行時,JavaScript 會有以下步驟:
構造器調用 base 方法,將傳遞給該構造器的參數中的兩個,作為參數傳遞給 base 方法,同時還傳遞一個字符串參數? "engineering"。顯式地在構造器中使用 "engineering" 表明所有 Engineer 對象繼承的 dept 屬性具有相同的值,且該值重載了繼承自 Employee 的值。
因為 base 是 Engineer 的一個方法,在調用 base 時,JavaScript 將在步驟 1 中創建的對象綁定給 this 關鍵字。這樣,WorkerBee 函數接著將 "Doe, Jane" 和 "engineering" 參數傳遞給 Employee 構造器函數。當從 Employee 構造器函數返回時,WorkerBee 函數用剩下的參數設置 projects 屬性。
您可以認為,在 Engineer 的構造器中調用了?WorkerBee 的構造器,也就為 Engineer 對象設置好了繼承關系。事實并非如此。調用 WorkerBee 構造器確保了Engineer 對象以所有在構造器中所指定的屬性被調用。但是,如果后續在 Employee 或者 WorkerBee 原型中添加了屬性,那些屬性不會被 Engineer 對象繼承。例如,假設如下語句:
function Engineer (name, projs, mach) {this.base = WorkerBee;this.base(name, "engineering", projs);this.machine = mach || ""; } var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau"); Employee.prototype.specialty = "none";對象 jane 不會繼承 specialty 屬性。您必須顯式地設置原型才能確保動態的繼承。如果修改成如下的語句:
function Engineer (name, projs, mach) {this.base = WorkerBee;this.base(name, "engineering", projs);this.machine = mach || ""; } Engineer.prototype = new WorkerBee; var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau"); Employee.prototype.specialty = "none";現在 jane 對象的 specialty 屬性為 "none" 了。
繼承的另一種途徑是使用call() / apply() 方法。下面的方式都是等價的:
| function Engineer (name, projs, mach) {this.base = WorkerBee;this.base(name, "engineering", projs);this.machine = mach || ""; } | function Engineer (name, projs, mach) {WorkerBee.call(this, name, "engineering", projs);this.machine = mach || ""; } |
使用 javascript 的 call() 方法相對明了一些,因為無需 base 方法了。
再談屬性的繼承
前面的小節中描述了 JavaScript 構造器和原型如何提供層級結構和繼承的實現。本節中對之前未討論的一些細節進行闡述。
本地值和繼承值
正如本章前面所述,在訪問一個對象的屬性時,JavaScript 將執行下面的步驟:
以上步驟的結果依賴于您是如何定義的。最早的例子中具有如下定義:
function Employee () {this.name = "";this.dept = "general"; }function WorkerBee () {this.projects = []; } WorkerBee.prototype = new Employee;基于這些定義,假定通過如下的語句創建 WorkerBee 的實例 amy:
var amy = new WorkerBee;則 amy 對象將具有一個本地屬性,projects。name 和 dept 屬性則不是 amy 對象本地的,而是從 amy 對象的 __proto__ 屬性獲得的。因此,amy 將具有如下的屬性值:
amy.name == ""; amy.dept == "general"; amy.projects == [];現在,假設修改了與?Employee 的相關聯原型中的 name 屬性的值:
Employee.prototype.name = "Unknown"乍一看,您可能覺得新的值會傳播給所有 Employee 的實例。然而,并非如此。
在創建 Employee 對象的任意實例時,該實例的 name 屬性將獲得一個本地值(空的字符串)。這就意味著在創建一個新的 Employee 對象作為 WorkerBee 的原型時,WorkerBee.prototype 的 name 屬性將具有一個本地值。因此,當 JavaScript 查找 amy 對象(WorkerBee 的實例)的 name 屬性時,JavaScript 將找到 WorkerBee.prototype 中的本地值。因此,也就不會繼續在原型鏈中向上找到 Employee.prototype 了。
如果想在運行時修改一個對象的屬性值并且希望該值被所有該對象的后代所繼承,您就不能在該對象的構造器函數中定義該屬性。而應該將該屬性添加到該對象所關聯的原型中。例如,假設將前面的代碼作如下修改:
function Employee () {this.dept = "general"; } Employee.prototype.name = "";function WorkerBee () {this.projects = []; } WorkerBee.prototype = new Employee;var amy = new WorkerBee;Employee.prototype.name = "Unknown";在這種情況下,amy 的 name 屬性將為 "Unknown"。
正如這些例子所示,如果希望對象的屬性具有默認值,并且希望在運行時修改這些默認值,應該在對象的原型中設置這些屬性,而不是在構造器函數中。
判斷實例的關系
JavaScript 的屬性查找機制首先在對象自身的屬性中查找,如果指定的屬性名稱沒有找到,將在對象的特殊屬性 __proto__ 中查找。這個過程是遞歸的;被稱為“在原型鏈中查找”。
特殊的 __proto__ 屬性是在構建對象時設置的;設置為構造器的 prototype 屬性的值。所以表達式 new Foo() 將創建一個對象,其 __proto__ == Foo.prototype。因而,修改 Foo.prototype 的屬性,將改變所有通過 new Foo() 創建的對象的屬性的查找。
每個對象都有一個 __proto__ 對象屬性(除了 Object);每個函數都有一個 prototype 對象屬性。因此,通過“原型繼承(prototype inheritance)”,對象與其它對象之間形成關系。通過比較對象的 __proto__ 屬性和函數的 prototype 屬性可以檢測對象的繼承關系。JavaScript 提供了便捷方法:instanceof 操作符可以用來將一個對象和一個函數做檢測,如果對象繼承自函數的原型,則該操作符返回真。例如:
var f = new Foo(); var isTrue = (f instanceof Foo);作為詳細一點的例子,假定我們使用和在 繼承屬性 中相同的一組定義。創建 Engineer 對象如下:
var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");對于該對象,以下所有語句均為真:
chris.__proto__ == Engineer.prototype; chris.__proto__.__proto__ == WorkerBee.prototype; chris.__proto__.__proto__.__proto__ == Employee.prototype; chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype; chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;基于此,可以寫出一個如下所示的 instanceOf 函數:
function instanceOf(object, constructor) {while (object != null) {if (object == constructor.prototype)return true;if (typeof object == 'xml') {return constructor.prototype == XML.prototype;}object = object.__proto__;}return false; } Note: 在上面的實現中,檢查對象的類型是否為 "xml" 的目的在于解決新近版本的 JavaScript 中表達 XML 對象的特異之處。如果您想了解其中瑣碎細節,可以參考 bug?634150。instanceOf (chris, Engineer) instanceOf (chris, WorkerBee) instanceOf (chris, Employee) instanceOf (chris, Object)但如下表達式為假:
instanceOf (chris, SalesPerson)構造器中的全局信息
在創建構造器時,在構造器中設置全局信息要小心。例如,假設希望為每一個雇員分配一個唯一標識。可能會為 Employee 使用如下定義:
var idCounter = 1;function Employee (name, dept) {this.name = name || "";this.dept = dept || "general";this.id = idCounter++; }基于該定義,在創建新的 Employee 時,構造器為其分配了序列中的下一個標識符。然后遞增全局的標識符計數器。因此,如果,如果隨后的語句如下,則 victoria.id 為 1 而 harry.id 為 2:
var victoria = new Employee("Pigbert, Victoria", "pubs") var harry = new Employee("Tschopik, Harry", "sales")乍一看似乎沒問題。但是,無論什么目的,在每一次創建 Employee 對象時,idCounter 都將被遞增一次。如果創建本章中所描述的整個 Employee 層級結構,每次設置原型的時候,Employee 構造器都將被調用一次。假設有如下代碼:
var idCounter = 1;function Employee (name, dept) {this.name = name || "";this.dept = dept || "general";this.id = idCounter++; }function Manager (name, dept, reports) {...} Manager.prototype = new Employee;function WorkerBee (name, dept, projs) {...} WorkerBee.prototype = new Employee;function Engineer (name, projs, mach) {...} Engineer.prototype = new WorkerBee;function SalesPerson (name, projs, quota) {...} SalesPerson.prototype = new WorkerBee;var mac = new Engineer("Wood, Mac");還可以進一步假設上面省略掉的定義中包含 base 屬性而且調用了原型鏈中高于它們的構造器。即便在現在這個情況下,在 mac 對象創建時,mac.id 為 5。
依賴于應用程序,計數器額外的遞增可能有問題,也可能沒問題。如果確實需要準確的計數器,則以下構造器可以作為一個可行的方案:
function Employee (name, dept) {this.name = name || "";this.dept = dept || "general";if (name)this.id = idCounter++; }在用作原型而創建新的 Employee 實例時,不會指定參數。使用這個構造器定義,如果不指定參數,構造器不會指定標識符,也不會遞增計數器。而如果想讓 Employee 分配到標識符,則必需為雇員指定姓名。在這個例子中,mac.id 將為 1。
沒有多繼承
某些面向對象語言支持多重繼承。也就是說,對象可以從無關的多個父對象中繼承屬性和屬性值。JavaScript 不支持多重繼承。
JavaScript 屬性值的繼承是在運行時通過檢索對象的原型鏈來實現的。因為對象只有一個原型與之關聯,所以 JavaScript 無法動態地從多個原型鏈中繼承。
在 JavaScript 中,可以在構造器函數中調用多個其它的構造器函數。這一點造成了多重繼承的假象。例如,考慮如下語句:
function Hobbyist (hobby) {this.hobby = hobby || "scuba"; }function Engineer (name, projs, mach, hobby) {this.base1 = WorkerBee;this.base1(name, "engineering", projs);this.base2 = Hobbyist;this.base2(hobby);this.machine = mach || ""; } Engineer.prototype = new WorkerBee;var dennis = new Engineer("Doe, Dennis", ["collabra"], "hugo")進一步假設使用本章前面所屬的 WorkerBee 的定義。此時 dennis 對象具有如下屬性:
dennis.name == "Doe, Dennis" dennis.dept == "engineering" dennis.projects == ["collabra"] dennis.machine == "hugo" dennis.hobby == "scuba"dennis 確實從 Hobbyist 構造器中獲得了 hobby 屬性。但是,假設添加了一個屬性到 Hobbyist 構造器的原型:
Hobbyist.prototype.equipment = ["mask", "fins", "regulator", "bcd"]dennis 對象不會繼承這個新屬性。
? 上一頁下一頁 ?
總結
- 上一篇: debugbar
- 下一篇: 索爱X10 常见问题汇总