摘要:對于采用這種模式的對象,還可以使用操作符確定它的類型寄生構造函數模式通常,在前述的幾種模式都不適用的情況下,可以使用寄生構造函數模式。這個模式可以在特殊的情況下用來為對象創建構造函數。
ECMA-262把對象定義為:“無序屬性的集合,其屬性可以包含基本值、對象或者函數”。嚴格來講,這就相當于說對象是一組沒有特定順序的值。
1 理解對象創建對象:
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); }; }1.1 屬性類型
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"
在嚴格模式下,上面的賦值操作將會導致拋出錯誤
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之后就會有限制了
1.1.2 訪問器屬性注意!利用Object.defineProperty()方法創建一個新的屬性時,如果不指定,configurable、enumerable和writable特性的默認值都是false。
訪問器有以下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
1.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; } } } });1.3 讀取屬性的特性
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"
2 創建對象對于訪問器屬性year,get是一個指向getter函數的指針
Object構造函數或對象字面量都可以用來創建單個對象,但這些方式有個明顯缺點:使用同一個接口創建很多對象,會產生大量的重復代碼。為解決這個問題,人們開始使用工廠模式的一種變體。
2.1 工廠模式工廠模式是軟件工程領域一種廣為人知的設計模式,這種模式抽象了創建具體對象的過程。考慮到在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");
2.2 構造函數模式工廠模式雖然解決了創建多個相似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
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
創建自定義的構造函數意味著將來可以將它的實例標識為一種特定的類型;而這正是構造函數模式勝過工廠模式的地方
2.2.1 將構造函數當作函數這種方式定義的構造函數是定義在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"2.2.2 構造函數的問題
使用構造函數的主要問題,就是每個方法都要在每個實例上重新創建一遍。在前面的例子中,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); };
2.3 原型模式這樣做解決了兩個函數做同一件事的問題,可是新問題又來了:在全局作用域中定義的函數實際上只能被某個對象調用,這讓全局作用域有點名不副實。更重要的是:如果對象需要定義很多方法,那么就要定義很多個全局函數,于是我們這個自定義的引用類型就絲毫沒有封裝性可言了。好在,原型模式可以解決這些問題
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); //true2.3.1 理解原型對象
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"2.3.3 更簡單的原型語法
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 });2.3.4 原型的動態性
在原型中查找值的過程是一次搜索
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
2.3.5 原生對象的原型重寫原型對象切斷了現有原型與任何之前已經存在的對象實例之間的聯系;它們引用的仍然是最初的原型
所有原生引用類型(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")); //true2.3.6 原型對象的問題
對于包含引用類型值得屬性來說,可能出現以下問題:
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); //true2.4 組合使用構造函數模式和原型模式
實例屬性都在構造函數中定義,所有實例共享的屬性和方法都在原型中定義:
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); } }
2.5 動態原型模式這種構造函數與原型混成的模式,是目前在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); } } }
2.6 寄生構造函數模式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操作符來確定對象類型。
2.7 穩妥構造函數模式由于存在上述問題,建議在可以使用其他模式的情況下,不要使用這種模式。
所謂穩妥對象,指的是沒有公共屬性,而且其方法也不引用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()方法外,沒有別的方式可以訪問其數據成員。即使有其他代碼會給這個對象添加方法或數據成員,但也不可能有別的辦法訪問傳入到構造函數中的原始數據。
3 繼承穩妥構造函數模式提供的這種安全性,使得它非常適合在某些安全執行環境下使用——例如,ADsafe和Caja提供的環境
許多OO語言都支持兩種繼承方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。由于函數沒有簽名,在ECMAScript中無法實現接口繼承。ECMAScript只支持實現繼承,而且其實現繼承主要是依靠原型鏈來實現的
3.1 原型鏈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()); //true3.1.1 別忘記默認的原型
所有引用類型默認都繼承了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)); //true3.1.3 謹慎地定義方法
子類型有時候需要覆蓋超類型中的某個方法,或者需要添加超類型中不存在的某個方法。但不管怎樣,給原型添加方法的代碼一定要放在替換原型的語句之后
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()); //error3.1.4 原型鏈的問題
包含引用類型值的原型會被所有實例共享。在通過原型來實現繼承時,原型實際上會變成另一個類型的實例。于是,原先的實例屬性也就變成了現在的原型屬性了
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屬性。
原型鏈的第二個問題:在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數傳遞參數。
3.2 借用構造函數在子類型構造函數的內部調用超類型構造函數
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"3.2.1 傳遞參數
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
3.3 組合繼承為了確保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(); //273.4 原型式繼承
原型式繼承并沒有使用嚴格意義上的構造函數。他的想法是借助原型可以基于已有的對象創建新對象,同時還不必因此創建自定義類型
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()方法的第二個參數格式相同:每個屬性都是通過自己的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性
在沒有必要興師動眾地創建構造函數,而只想讓一個對象與另一個對象保持類似的情況下,原型試繼承時完全可以勝任的。不過別忘了,包含引用類型值的屬性始終都會共享相應的值,就像使用原型模式一樣。
3.5 寄生式繼承寄生式繼承是與原型試繼承緊密相關的一種思路。寄生式繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再像真的是它做了所有工作一樣返回對象。一下代碼規范了寄生式繼承模式:
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()函數不是必須的;任何能夠返回新對象的函數都適用于此模式
3.6 寄生組合式繼承通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法
寄生組合式繼承的基本模式如下:
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()。
開發人員普遍認為寄生組合式繼承時引用類型最理想的繼承方式。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/82806.html
摘要:創建一個新對象將構造函數的作用域賦給新對象因此就指向了這個新對象執行構造函數中的代碼為這個新對象添加屬性返回新對象。 本章內容 理解對象屬性 理解并創建對象 理解繼承 ECMA-262把對象定義為:無序屬性的集合,其屬性可以包含基本值、對象或者函數 理解對象 創建對象 創建自定義對象的最簡單方式就是創建一個Object的實例,再為它添加屬性和方法。 var person = new...
摘要:高程讀書筆記第六章理解對象創建自定義對象的方式有創建一個實例,然后為它添加屬性和方法。創建了自定義的構造函數之后,其原型對象默認只會取得屬性至于其他方法都是從繼承而來的。 JS高程讀書筆記--第六章 理解對象 創建自定義對象的方式有創建一個Object實例,然后為它添加屬性和方法。還可用創建對象字面量的方式 屬性類型 ECMAScript在定義只有內部采用的特性時,描述了屬性的各種特征...
摘要:高程第六章繼承理解與實踐昨日細細的讀了一遍高程現在寫篇文章來鞏固下認知吧讀首先是從中讀到了什么我自己也在讀書的時候用筆記下了各個部分的點現在等于閱讀筆記回憶下書本理解基礎第五版中規定了兩種屬性數據屬性訪問器屬性數據屬性包含一個數據值的位 JavaScript高程第六章:繼承-理解與實踐昨日細細的讀了一遍JavaScript高程,現在寫篇文章來鞏固下認知吧. 讀 首先是從中讀到了什么,我...
摘要:把原型修改為另外一個對象就等于切斷了構造函數與最初原型之間的聯系。組合使用構造函數模式動態原型模式通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。 理解對象 屬性類型 數據屬性 數據屬性包含一個數據值的位置。在這個位置可以讀取和寫入值。數據屬性有 4 個描述其行為的特性。 [[Configurable]] :表示能否通過 delete 刪除屬性從而重新定義屬性,能否修...
摘要:三種使用構造函數創建對象的方法和的作用都是在某個特殊對象的作用域中調用函數。這種方式還支持向構造函數傳遞參數。叫法上把函數叫做構造函數,其他無區別適用情境可以在特殊的情況下用來為對象創建構造函數。 一、工廠模式 工廠模式:使用字面量和object構造函數會有很多重復代碼,在此基礎上改進showImg(https://segmentfault.com/img/bVbmKxb?w=456&...
閱讀 3184·2021-09-10 10:51
閱讀 3363·2021-08-31 09:38
閱讀 1656·2019-08-30 15:54
閱讀 3144·2019-08-29 17:22
閱讀 3224·2019-08-26 13:53
閱讀 1976·2019-08-26 11:59
閱讀 3292·2019-08-26 11:37
閱讀 3322·2019-08-26 10:47