ECMA-262把對象定義爲:「無序屬性的集合,其屬性能夠包含基本值、對象或者函數」。嚴格來說,這就至關於說對象是一組沒有特定順序的值。javascript
建立對象:java
var person = new Object(); person.name = "Nicholas"; person.age = 29; person.job = "Software Engineer"; person.sayName = function(){ alert(this.name); };
字面量形式:設計模式
var person = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function(){ alert(this.name); }; }
ECMA-262
第5版在定義只有內部才用的特性時,描述了屬性的各類特性。ECMA-262
定義這些特性是爲了實現JavaScript
引擎用的,所以在JavaScript中不能直接訪問它們。ECMAScript
中有兩種屬性:數據屬性和訪問器屬性。數組
數據屬性包含一個數據值的位置。在這個位置能夠讀取和寫入值。數據屬性有4個描述其行爲的特性瀏覽器
[[Configurable]]
:表示可否經過delete
刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成訪問器屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性的屬性值爲true
。[[Enumerable]]
:表示可否經過for-in
循環返回屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性的默認值爲true
。[[Writable]]
:包含這個屬性的數據值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。這個特性的默認值爲undefined
。要修改屬性默認的特性,必須使用ECMAScript 5
的Object.defineProperty()
方法:安全
var person = {}; Object.defineProperty(person, "name", { writable : false, value : "Nicholas" }); console.log(person.name); //"Nicholas" person.name = "Greg"; console.log(person.name); //"Nicholas"
在嚴格模式下,上面的賦值操做將會致使拋出錯誤app
var person = {}; Object.defineProperty(person, "name", { configurable : false, value : "Nicholas" }); console.log(person.name); //"Nicholas" delete person.name; console.log(person.name); //"Nicholas"
一旦把屬性定義爲不可配置的,就不能再把它變回可配置了:函數
var person = {}; Object.defineProperty(person, "name", { configurable : false, value : "Nicholas" }); Object.defineProperty(person, "name", { configurable : true, //拋出錯誤 value : "Nicholas" });
也就是說,能夠屢次調用Object.defineProperty()
方法修改同一個屬性,但在把configurable
特性設置爲false
以後就會有限制了測試
注意!利用
Object.defineProperty()
方法建立一個新的屬性時,若是不指定,configurable
、enumerable
和writable
特性的默認值都是false
。this
訪問器有如下4個屬性:
[[Configurable]]
:表示可否經過delete
刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成訪問器屬性。對於直接在對象上定義的屬性,這個特性的屬性值爲true
。[[Enumerable]]
:表示可否經過for-in
循環返回屬性。對於直接在對象上定義的屬性,這個特性的默認值爲true
。[[Get]]
:在讀取屬性時調用的函數。默認值爲undefined
[[Set]]
:在寫入屬性時調用的函數。默認值爲undefined
訪問器屬性不能直接定義,必須使用Object.defineProperty()
來定義:
var book = { _year : 2004, edition : 1 }; Object.defineProperty(book, "year", { get : function(){ return this._year; }, set : function(){ if(newValue > 2004){ this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; console.log(book.edition); //2
不必定非要同時指定getter和setter。只指定getter意味着屬性是不能寫,嘗試寫入屬性會被忽略。
Object.defineProperties()
:利用這個方法能夠經過描述符一次定義多個屬性var book = {}; Object.defineProperties(book, { _year : { writable : true, value : 2004 }, edition : { writable : true, value : 1 }, year : { get : function(){ return this._year; }, set : function(){ if(newValue > 2004){ this._year = newValue; this.edition += newValue - 2004; } } } });
Object.getOwnPropertyDescriptor()
:能夠取得給定屬性的描述符//接上段代碼 var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); console.log(descriptor.value); //2004 console.log(descriptor.configurable); //false console.log(typeof descriptor.get); //"undefined" var descriptor = Object.getOwnPropertyDescriptor(book, "year"); console.log(descriptor.value); //undefined console.log(descriptor.enumerable); //false console.log(typeof descriptor.get); //"function"
對於訪問器屬性
year
,get
是一個指向getter
函數的指針
Object
構造函數或對象字面量均可以用來建立單個對象,但這些方式有個明顯缺點:使用同一個接口建立不少對象,會產生大量的重複代碼。爲解決這個問題,人們開始使用工廠模式的一種變體。
工廠模式是軟件工程領域一種廣爲人知的設計模式,這種模式抽象了建立具體對象的過程。考慮到在ECMAScript
中沒法建立類,開發人員就發明了一種函數,用函數來封裝以特定接口建立對象的細節:
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ console.log(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
工廠模式雖然解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { console.log(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
構造函數模式與工廠模式的不一樣之處:
this
對象;return
語句。按照慣例,構造函數始終都應該以一個大寫字母開頭,而非構造函數則應該以一個小寫字母開頭。
要建立Person
的新實例,必須使用new
操做符。以這種方式調用構造函數實際上會經歷一下4個步驟:
this
就指向了這個新對象);console.log(person1.constructor == truPersone); //true console.log(person2.constructor == Person); //true console.log(person1 instanceof Object); //true console.log(person1 instanceof Person); //true console.log(person2 instanceof Object); //true console.log(person2 instanceof Person); //true
建立自定義的構造函數意味着未來能夠將它的實例標識爲一種特定的類型;而這正是構造函數模式賽過工廠模式的地方
這種方式定義的構造函數是定義在
Global
對象(在瀏覽器中是window
對象)中的
//看成構造函數使用 var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas" //做爲普通函數調用 Person("Greg", 27, "Doctor"); //嚴格模式下會拋出錯誤! window.sayName(); //"Greg" //在另外一個對象的做用域中調用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
使用構造函數的主要問題,就是每一個方法都要在每一個實例上從新建立一遍。在前面的例子中,person1
和person2
都有一個名爲sayName()
的方法,但那兩個方法不是同一個Function
的實例
不要忘了——
ECMAScript
中的函數是對象,所以每定義一個函數,也就是實例化了一個對象。從邏輯角度講,此時的構造函數也能夠這樣定義:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = new Function("console.log(this.name);"); //此聲明函數在邏輯上是等價的 } console.log(person1.sayName == person2.sayName); //false
建立兩個完成一樣任務的Function
實例的確沒有必要;何況有this
對象在,根本不用在執行代碼前就把函數綁定到特定對象上。因而能夠將函數定義轉移到構造函數外部來解決這個問題:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { console.log(this.name); };
這樣作解決了兩個函數作同一件事的問題,但是新問題又來了:在全局做用域中定義的函數實際上只能被某個對象調用,這讓全局做用域有點名存實亡。更重要的是:若是對象須要定義不少方法,那麼就要定義不少個全局函數,因而咱們這個自定義的引用類型就絲毫沒有封裝性可言了。好在,原型模式能夠解決這些問題
function Person() { } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" console.log(person1.sayName == person2.sayName); //true
console.log(Person.prototype.isPrototypeOf(person1)); //true console.log(Person.prototype.isPrototypeOf(person2)); //true
ECMAScript 5
增長了一個新方法,叫Object.getPrototypeOf()
,這個方法返回[[Prototype]]
的值
console.log(Object.getPrototypeOf(person1) == Person.prototype); //true console.log(Object.getPrototypeOf(person1).name); //"Nicholas"
不能經過對象實例重寫原型中的值。若是在實例中添加一個屬性,而該屬性與實例原型中的一個屬性同名,那麼該屬性將會屏蔽原型中的那個屬性:
function Person() { } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); var person2 = new Person(); console.log(person1.hasOwnProperty("name")); //false console.log("name" in person1); //true person1.name = "Greg"; console.log(person1.name); //"Greg"——來自實例 console.log(person1.hasOwnProperty("name")); //true console.log("name" in person1); //true console.log(person2.name); //"Nicholas"——來自原型 console.log(person2.hasOwnProperty("name")); //false delete person1.name; console.log(person1.name); //"Nicholas"——來自原型
in
操做符只要經過對象可以訪問到屬性就返回true- 使用
hasOwnProperty()
方法能夠檢測一個屬性是存在於實例中,仍是存在於原型中。這個方法(不要忘了它是從Object
繼承來的)只在給定屬性存在於對象實例中時,纔會返回true
function hasPrototypeProperty(object, name) { return !object.hasOwnProperty(name) && (name in object); } //返回true則代表該屬性存在於原型中 //返回false則代表該屬性存在於實例中
在使用for-in
循環時,返回的是全部可以經過對象訪問的、可枚舉的屬性,其中包括實例和原型中的屬性。
ECMAScript 5
的Object.keys()
方法能夠取得對象上全部可枚舉的實例屬性var keys = Object.keys(Person.prototype); alert(keys); //"name, age, job, sayName" //keys中保存一個數組,數組中是字符串"name, age, job, sayName"。
Object.getownPropertyNames()
能夠得到全部實例屬性,不管它是否可枚舉var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); //"constructor, name, age, job, sayName"
function Person() { } Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { console.log(this.name); } };
上面代碼將Person.prototype
設置爲等於一個以對象字面量形式建立的新對象。結果相同,但constructor
屬性再也不指向Person
了。所以上面使用的語法,本質上徹底重寫了默認的prototype
對象,所以constructor
屬性也就變成了新對象的constructor
屬性(指向Object
構造函數),再也不指向Person
函數
var friend = new Person(); console.log(friend instanceof Object); //true console.log(friend instanceof Person); //true console.log(friend.constructor == Object); //false console.log(friend.constructor == Person); //true
若是constructor
的值真的很重要,能夠像下面這樣特地將它設置回恰當的值:
function Person() { } Person.prototype = { constructor : Person //…… };
這種方式重設
constructor
屬性會致使它的[[Enumerable]]
特性被設置爲true
,默認狀況下,原聲的constructor
屬性是不可枚舉的
所以若是你使用兼容ECMAScript 5
的JavaScript
引擎,能夠試一試Object.defineProperty()
Object.defineProperty(Person.prototype, "constructor", { enumerable : false, value : Person });
在原型中查找值的過程是一次搜索
var friend = new Person(); Person.prototype.sayHi = function(){ console.log("hi"); } friend.sayHi(); //"hi" (沒有問題!)
重寫原型對象:
function Person(){ } var friend = new Person(); Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { console.log(this.name); } }; friend.sayName(); //error
重寫原型對象切斷了現有原型與任何以前已經存在的對象實例之間的聯繫;它們引用的仍然是最初的原型
全部原生引用類型(Object、Array、String
等)都在其構造函數的原型上定義了方法
console.log(typeof Array.prototype.sort); //"function" console.log(typeof String.prototype.substring); //"function"
給原生對象的原型添加方法:
String.prototype.startsWith = function(text){ return this.indexOf(text) == 0; } var msg = "Hello world!"; console.log(msg.startsWith("Hello")); //true
對於包含引用類型值得屬性來講,可能出現如下問題:
function Person() { } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { console.log(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); console.log(person1.friends); console.log(person2.friends); console.log(person1.friends === person2.friends); //true
實例屬性都在構造函數中定義,全部實例共享的屬性和方法都在原型中定義:
function Person(name, age, job) { this.name = name, this.age = age, this.job = job, this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor : Person, sayName : function () { console.log(this.name); } }
這種構造函數與原型混成的模式,是目前在ECMAScript中使用最普遍、認同度最高的一種建立自定義類型的方法。能夠說,這是用來定義引用類型的一種默認模式
有其餘OO語言經驗的開發人員在看到獨立的構造函數和原型時,極可能會感到很是困惑。動態原型模式正式致力於解決這個問題的一個方案
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; //方法 if(typeof this.sayName != "function"){ Person.prototype.sayName = function () { console.log(this.name); } } }
if語句檢查的能夠是初始化以後應該存在的任何屬性和方法——沒必要用一大堆if語句檢查每一個屬性和每一個方法;只要檢查其中一個便可。對於採用這種模式的對象,還可使用instanceof操做符肯定它的類型
一般,在前述的幾種模式都不適用的狀況下,可使用寄生構造函數模式。這種模式的基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象;但從表面上看,這個函數又很像是典型的構造函數:
function Person(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { console.log(this.name); }; return o; } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
構造函數在不返回值的狀況下,默認會返回新對象實例。而經過在構造函數的末尾添加一個return
語句,能夠重寫調用構造函數時返回的值。
這個模式能夠在特殊的狀況下用來爲對象建立構造函數。假設咱們想建立一個具備額外方法的特殊數組。因爲不能直接修改Array
構造函數,所以可使用這個模式:
function SpecialArray() { //建立數組 var values = new Array(); //添加值 values.push.apply(values, arguments); //添加方法 values.toPipedString = function () { return this.join("|"); } //返回數組 return values; } var colors = new SpecialArray("red", "blue", "green"); console.log(colors.toPipedString()); //"red|blue|green"
注意:返回的對象與構造函數或者與構造函數的原型屬性直接沒有關係;也就是說,構造函數返回的對象與在構造函數外部建立的對象沒有什麼不一樣。爲此,不能依賴instanceof
操做符來肯定對象類型。
因爲存在上述問題,建議在可使用其餘模式的狀況下,不要使用這種模式。
所謂穩妥對象,指的是沒有公共屬性,並且其方法也不引用this的對象。穩妥對象最合適在一些安全的環境中(這些環境中會禁止使用this
和new
),或者在防止數據被其餘應用程序(如Mashup程序)改動時使用。
function Person(name, age, job) { //建立要返回的對象 var o = new Object(); //能夠在這裏定義私有變量和函數 //添加方法 o.sayName = function () { console.log(name); } return o; } var friend = Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
變量friend
中保存的是一個穩妥對象,而除了調用sayName()
方法外,沒有別的方式能夠訪問其數據成員。即便有其餘代碼會給這個對象添加方法或數據成員,但也不可能有別的辦法訪問傳入到構造函數中的原始數據。
穩妥構造函數模式提供的這種安全性,使得它很是適合在某些安全執行環境下使用——例如,
ADsafe
和Caja
提供的環境
許多OO語言
都支持兩種繼承方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。因爲函數沒有簽名,在ECMAScript
中沒法實現接口繼承。ECMAScript
只支持實現繼承,並且其實現繼承主要是依靠原型鏈來實現的
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType() { this.subProperty = false; } //繼承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subProperty; }; var instance = new SubType(); console.log(instance.getSuperValue()); //true
全部引用類型默認都繼承了Object
,而這個繼承也是經過原型鏈實現的。
instanceof
:只要用這個操做符來測試實例與原型鏈中出現過的構造函數,結果就會返回true
console.log(instance instanceof Object); //true console.log(instance instanceof SuperType); //true console.log(instance instanceof SubType); //true
isPrototypeOf
:只要是原型鏈中出現過的原型,均可以說是該原型鏈所派生的實例的原型,所以該方法也會返回true
console.log(Object.prototype.isPrototypeOf(instance)); //true console.log(SuperType.prototype.isPrototypeOf(instance)); //true console.log(SubType.prototype.isPrototypeOf(instance)); //true
子類型有時候須要覆蓋超類型中的某個方法,或者須要添加超類型中不存在的某個方法。但無論怎樣,給原型添加方法的代碼必定要放在替換原型的語句以後
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType() { this.subProperty = false; } //繼承了SuperType SubType.prototype = new SuperType(); //添加新方法 SubType.prototype.getSubValue = function () { return this.subProperty; }; //重寫超類型中的方法 SubType.prototype.getSuperValue = function(){ return false; } var instance = new SubType(); console.log(instance.getSuperValue()); //false
getSuperValue()
是原型鏈中已經存在的一個方法,重寫這個方法將會屏蔽原來的那個方法。當經過SubType
的實例調用getSuperValue()
時,調用的就是這個從新定義的方法;但經過SuperType
的實例調用getSuperValue()
時,還會繼續調用原來的那個方法
在經過原型鏈屬性繼承時,不能使用對象字面量建立原型方法。由於這樣就會重寫原型鏈:
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType() { this.subProperty = false; } //繼承了SuperType SubType.prototype = new SuperType(); //使用字面量添加新方法,會致使上一行代碼無效 SubType.prototype = { //…… } var instance = new SubType(); console.log(instance.getSuperValue()); //error
包含引用類型值的原型會被全部實例共享。在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例。因而,原先的實例屬性也就變成了如今的原型屬性了
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //繼承了SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); console.log(instance2.colors); //"red,blue,green,black"
SubType
的全部實例都會共享這一個colors
屬性。
原型鏈的第二個問題:在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響全部對象實例的狀況下,給超類型的構造函數傳遞參數。
在子類型構造函數的內部調用超類型構造函數
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //繼承了SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); console.log(instance2.colors); //"red,blue,green"
function SuperType(name){ this.name = name; } function SubType(){ //繼承了SuperType,同時還傳遞了參數 SuperType.call(this, "Nicholas"); //實例屬性 this.age = 29; } var instance = new SubType(); console.log(instance.name); //"Nicholas"; console.log(instance.age); //29
爲了確保
SuperType
構造函數不會重寫子類型的屬性,能夠在調用超類型構造函數後,再添加應該在子類型中定義的屬性
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢,成爲JavaScript
中最經常使用的繼承模式。並且,instanceof
和isPrototypeOf()
也能狗用於識別基於組合繼承建立的對象。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function (){ console.log(this.name); } function SubType(name, age){ //繼承屬性 SuperType.call(this, name); this.age = age; } //繼承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function (){ console.log(this.age); } var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); console.log(instance1.colors); //["red", "blue", "green", "black"] instance1.sayName(); //Nicholas instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); console.log(instance2.colors); //["red", "blue", "green"] instance2.sayName(); //Greg instance2.sayAge(); //27
原型式繼承並無使用嚴格意義上的構造函數。他的想法是藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型
function object(o){ function F(){} F.prototype = o; return new F(); } var person = { name : "Nicholas", friends : ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); //["Shelby", "Court", "Van", "Rob", "Barbie"]
在
object()
函數內部,先建立了一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例。從本質上講,object()
隊傳入其中的對象執行了一次淺複製
ECMAScript 5經過新增Object.create()
方法規範化了原型式繼承:
var person = { name : "Nicholas", friends : ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); //["Shelby", "Court", "Van", "Rob", "Barbie"]
在傳入一個參數的狀況下,
Object.create()
與object()
方法的行爲相同
var person = { name : "Nicholas", friends : ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person, { name : { value : "Greg" } }); console.log(anotherPerson.name); //"Greg"
Object.create()
方法的第二個參數與Object.defineProperties()
方法的第二個參數格式相同:每一個屬性都是經過本身的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性
在沒有必要興師動衆地建立構造函數,而只想讓一個對象與另外一個對象保持相似的狀況下,原型試繼承時徹底能夠勝任的。不過別忘了,包含引用類型值的屬性始終都會共享相應的值,就像使用原型模式同樣。
寄生式繼承是與原型試繼承緊密相關的一種思路。寄生式繼承的思路與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像真的是它作了全部工做同樣返回對象。一下代碼規範了寄生式繼承模式:
function createAnother(original){ var clone = object(original); //經過調用函數建立一個新對象 clone.sayHi = function(){ //以某種方式來加強這個對象 console.log("hi"); }; return clone; //返回這個對象 }
能夠像下面這樣來使用createAnother()
函數:
var person = { name : "Nicholas", friends : ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi"
新對象不只具備person的全部屬性和方法,並且還有本身的sayHi()方法
在主要考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。前面示範繼承模式時使用的object()
函數不是必須的;任何可以返回新對象的函數都適用於此模式
經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法
寄生組合式繼承的基本模式以下:
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //建立對象 prototype.constructor = subType; //加強對象 subType.prototype = prototype; //指定對象 } function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function (){ console.log(this.name); }; function SubType(name, age){ SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function (){ console.log(this.age); };
這個例子的高效率體如今它只調用了一次SuperType
構造函數,而且所以避免了再SubType.prototype
上面建立沒必要要的、多餘的屬性。與此同時,原型鏈還能保持不變;所以,還可以正常使用instanceof
和isPrototypeOf()
。
開發人員廣泛認爲寄生組合式繼承時引用類型最理想的繼承方式。