摘要:對(duì)于采用這種模式的對(duì)象,還可以使用操作符確定它的類(lèi)型寄生構(gòu)造函數(shù)模式通常,在前述的幾種模式都不適用的情況下,可以使用寄生構(gòu)造函數(shù)模式。這個(gè)模式可以在特殊的情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù)。
ECMA-262把對(duì)象定義為:“無(wú)序?qū)傩缘募希鋵傩钥梢园局怠?duì)象或者函數(shù)”。嚴(yán)格來(lái)講,這就相當(dāng)于說(shuō)對(duì)象是一組沒(méi)有特定順序的值。
1 理解對(duì)象創(chuàng)建對(duì)象:
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 屬性類(lèi)型
ECMA-262第5版在定義只有內(nèi)部才用的特性時(shí),描述了屬性的各種特性。ECMA-262定義這些特性是為了實(shí)現(xiàn)JavaScript引擎用的,因此在JavaScript中不能直接訪問(wèn)它們。
ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問(wèn)器屬性。
數(shù)據(jù)屬性包含一個(gè)數(shù)據(jù)值的位置。在這個(gè)位置可以讀取和寫(xiě)入值。數(shù)據(jù)屬性有4個(gè)描述其行為的特性
[[Configurable]]:表示能否通過(guò)delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問(wèn)器屬性。像前面例子中那樣直接在對(duì)象上定義的屬性,它們的這個(gè)特性的屬性值為true。
[[Enumerable]]:表示能否通過(guò)for-in循環(huán)返回屬性。像前面例子中那樣直接在對(duì)象上定義的屬性,它們的這個(gè)特性的默認(rèn)值為true。
[[Writable]]:包含這個(gè)屬性的數(shù)據(jù)值。讀取屬性值的時(shí)候,從這個(gè)位置讀;寫(xiě)入屬性值的時(shí)候,把新值保存在這個(gè)位置。這個(gè)特性的默認(rèn)值為undefined。
要修改屬性默認(rèn)的特性,必須使用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"
在嚴(yán)格模式下,上面的賦值操作將會(huì)導(dǎo)致拋出錯(cuò)誤
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, //拋出錯(cuò)誤 value : "Nicholas" });
也就是說(shuō),可以多次調(diào)用Object.defineProperty()方法修改同一個(gè)屬性,但在把configurable特性設(shè)置為false之后就會(huì)有限制了
1.1.2 訪問(wèn)器屬性注意!利用Object.defineProperty()方法創(chuàng)建一個(gè)新的屬性時(shí),如果不指定,configurable、enumerable和writable特性的默認(rèn)值都是false。
訪問(wèn)器有以下4個(gè)屬性:
[[Configurable]]:表示能否通過(guò)delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問(wèn)器屬性。對(duì)于直接在對(duì)象上定義的屬性,這個(gè)特性的屬性值為true。
[[Enumerable]]:表示能否通過(guò)for-in循環(huán)返回屬性。對(duì)于直接在對(duì)象上定義的屬性,這個(gè)特性的默認(rèn)值為true。
[[Get]]:在讀取屬性時(shí)調(diào)用的函數(shù)。默認(rèn)值為undefined
[[Set]]:在寫(xiě)入屬性時(shí)調(diào)用的函數(shù)。默認(rèn)值為undefined
訪問(wèn)器屬性不能直接定義,必須使用Object.defineProperty()來(lái)定義:
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 定義多個(gè)屬性不一定非要同時(shí)指定getter和setter。只指定getter意味著屬性是不能寫(xiě),嘗試寫(xiě)入屬性會(huì)被忽略。
Object.defineProperties():利用這個(gè)方法可以通過(guò)描述符一次定義多個(gè)屬性
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 創(chuàng)建對(duì)象對(duì)于訪問(wèn)器屬性year,get是一個(gè)指向getter函數(shù)的指針
Object構(gòu)造函數(shù)或?qū)ο笞置媪慷伎梢杂脕?lái)創(chuàng)建單個(gè)對(duì)象,但這些方式有個(gè)明顯缺點(diǎn):使用同一個(gè)接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼。為解決這個(gè)問(wèn)題,人們開(kāi)始使用工廠模式的一種變體。
2.1 工廠模式工廠模式是軟件工程領(lǐng)域一種廣為人知的設(shè)計(jì)模式,這種模式抽象了創(chuàng)建具體對(duì)象的過(guò)程。考慮到在ECMAScript中無(wú)法創(chuàng)建類(lèi),開(kāi)發(fā)人員就發(fā)明了一種函數(shù),用函數(shù)來(lái)封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié):
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 構(gòu)造函數(shù)模式工廠模式雖然解決了創(chuàng)建多個(gè)相似對(duì)象的問(wèn)題,但卻沒(méi)有解決對(duì)象識(shí)別的問(wèn)題(即怎樣知道一個(gè)對(duì)象的類(lèi)型)。
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");
構(gòu)造函數(shù)模式與工廠模式的不同之處:
沒(méi)有顯式地創(chuàng)建對(duì)象;
直接將屬性和方法賦給了this對(duì)象;
沒(méi)有return語(yǔ)句。
按照慣例,構(gòu)造函數(shù)始終都應(yīng)該以一個(gè)大寫(xiě)字母開(kāi)頭,而非構(gòu)造函數(shù)則應(yīng)該以一個(gè)小寫(xiě)字母開(kāi)頭。
要?jiǎng)?chuàng)建Person的新實(shí)例,必須使用new操作符。以這種方式調(diào)用構(gòu)造函數(shù)實(shí)際上會(huì)經(jīng)歷一下4個(gè)步驟:
創(chuàng)建一個(gè)對(duì)象;
將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象);
執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性);
返回新對(duì)象。
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
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來(lái)可以將它的實(shí)例標(biāo)識(shí)為一種特定的類(lèi)型;而這正是構(gòu)造函數(shù)模式勝過(guò)工廠模式的地方
2.2.1 將構(gòu)造函數(shù)當(dāng)作函數(shù)這種方式定義的構(gòu)造函數(shù)是定義在Global對(duì)象(在瀏覽器中是window對(duì)象)中的
//當(dāng)作構(gòu)造函數(shù)使用 var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas" //作為普通函數(shù)調(diào)用 Person("Greg", 27, "Doctor"); //嚴(yán)格模式下會(huì)拋出錯(cuò)誤! window.sayName(); //"Greg" //在另一個(gè)對(duì)象的作用域中調(diào)用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"2.2.2 構(gòu)造函數(shù)的問(wèn)題
使用構(gòu)造函數(shù)的主要問(wèn)題,就是每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。在前面的例子中,person1和person2都有一個(gè)名為sayName()的方法,但那兩個(gè)方法不是同一個(gè)Function的實(shí)例
不要忘了——ECMAScript中的函數(shù)是對(duì)象,因此每定義一個(gè)函數(shù),也就是實(shí)例化了一個(gè)對(duì)象。從邏輯角度講,此時(shí)的構(gòu)造函數(shù)也可以這樣定義:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = new Function("console.log(this.name);"); //此聲明函數(shù)在邏輯上是等價(jià)的 } console.log(person1.sayName == person2.sayName); //false
創(chuàng)建兩個(gè)完成同樣任務(wù)的Function實(shí)例的確沒(méi)有必要;況且有this對(duì)象在,根本不用在執(zhí)行代碼前就把函數(shù)綁定到特定對(duì)象上。于是可以將函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部來(lái)解決這個(gè)問(wèn)題:
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 原型模式這樣做解決了兩個(gè)函數(shù)做同一件事的問(wèn)題,可是新問(wèn)題又來(lái)了:在全局作用域中定義的函數(shù)實(shí)際上只能被某個(gè)對(duì)象調(diào)用,這讓全局作用域有點(diǎn)名不副實(shí)。更重要的是:如果對(duì)象需要定義很多方法,那么就要定義很多個(gè)全局函數(shù),于是我們這個(gè)自定義的引用類(lèi)型就絲毫沒(méi)有封裝性可言了。好在,原型模式可以解決這些問(wèn)題
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 理解原型對(duì)象
console.log(Person.prototype.isPrototypeOf(person1)); //true console.log(Person.prototype.isPrototypeOf(person2)); //true
ECMAScript 5增加了一個(gè)新方法,叫Object.getPrototypeOf(),這個(gè)方法返回[[Prototype]]的值
console.log(Object.getPrototypeOf(person1) == Person.prototype); //true console.log(Object.getPrototypeOf(person1).name); //"Nicholas"
不能通過(guò)對(duì)象實(shí)例重寫(xiě)原型中的值。如果在實(shí)例中添加一個(gè)屬性,而該屬性與實(shí)例原型中的一個(gè)屬性同名,那么該屬性將會(huì)屏蔽原型中的那個(gè)屬性:
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"——來(lái)自實(shí)例 console.log(person1.hasOwnProperty("name")); //true console.log("name" in person1); //true console.log(person2.name); //"Nicholas"——來(lái)自原型 console.log(person2.hasOwnProperty("name")); //false delete person1.name; console.log(person1.name); //"Nicholas"——來(lái)自原型
in操作符只要通過(guò)對(duì)象能夠訪問(wèn)到屬性就返回true
使用hasOwnProperty()方法可以檢測(cè)一個(gè)屬性是存在于實(shí)例中,還是存在于原型中。這個(gè)方法(不要忘了它是從Object繼承來(lái)的)只在給定屬性存在于對(duì)象實(shí)例中時(shí),才會(huì)返回true
function hasPrototypeProperty(object, name) { return !object.hasOwnProperty(name) && (name in object); } //返回true則表明該屬性存在于原型中 //返回false則表明該屬性存在于實(shí)例中
在使用for-in循環(huán)時(shí),返回的是所有能夠通過(guò)對(duì)象訪問(wèn)的、可枚舉的屬性,其中包括實(shí)例和原型中的屬性。
ECMAScript 5的Object.keys()方法可以取得對(duì)象上所有可枚舉的實(shí)例屬性
var keys = Object.keys(Person.prototype); alert(keys); //"name, age, job, sayName" //keys中保存一個(gè)數(shù)組,數(shù)組中是字符串"name, age, job, sayName"。
Object.getownPropertyNames()可以獲得所有實(shí)例屬性,無(wú)論它是否可枚舉
var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); //"constructor, name, age, job, sayName"2.3.3 更簡(jiǎn)單的原型語(yǔ)法
function Person() { } Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { console.log(this.name); } };
上面代碼將Person.prototype設(shè)置為等于一個(gè)以對(duì)象字面量形式創(chuàng)建的新對(duì)象。結(jié)果相同,但constructor屬性不再指向Person了。因此上面使用的語(yǔ)法,本質(zhì)上完全重寫(xiě)了默認(rèn)的prototype對(duì)象,因此constructor屬性也就變成了新對(duì)象的constructor屬性(指向Object構(gòu)造函數(shù)),不再指向Person函數(shù)
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的值真的很重要,可以像下面這樣特意將它設(shè)置回恰當(dāng)?shù)闹担?/p>
function Person() { } Person.prototype = { constructor : Person //…… };
這種方式重設(shè)constructor屬性會(huì)導(dǎo)致它的[[Enumerable]]特性被設(shè)置為true,默認(rèn)情況下,原聲的constructor屬性是不可枚舉的
因此如果你使用兼容ECMAScript 5的JavaScript引擎,可以試一試Object.defineProperty()
Object.defineProperty(Person.prototype, "constructor", { enumerable : false, value : Person });2.3.4 原型的動(dòng)態(tài)性
在原型中查找值的過(guò)程是一次搜索
var friend = new Person(); Person.prototype.sayHi = function(){ console.log("hi"); } friend.sayHi(); //"hi" (沒(méi)有問(wèn)題!)
重寫(xiě)原型對(duì)象:
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 原生對(duì)象的原型重寫(xiě)原型對(duì)象切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對(duì)象實(shí)例之間的聯(lián)系;它們引用的仍然是最初的原型
所有原生引用類(lèi)型(Object、Array、String等)都在其構(gòu)造函數(shù)的原型上定義了方法
console.log(typeof Array.prototype.sort); //"function" console.log(typeof String.prototype.substring); //"function"
給原生對(duì)象的原型添加方法:
String.prototype.startsWith = function(text){ return this.indexOf(text) == 0; } var msg = "Hello world!"; console.log(msg.startsWith("Hello")); //true2.3.6 原型對(duì)象的問(wèn)題
對(duì)于包含引用類(lèi)型值得屬性來(lái)說(shuō),可能出現(xiàn)以下問(wèn)題:
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 組合使用構(gòu)造函數(shù)模式和原型模式
實(shí)例屬性都在構(gòu)造函數(shù)中定義,所有實(shí)例共享的屬性和方法都在原型中定義:
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 動(dòng)態(tài)原型模式這種構(gòu)造函數(shù)與原型混成的模式,是目前在ECMAScript中使用最廣泛、認(rèn)同度最高的一種創(chuàng)建自定義類(lèi)型的方法。可以說(shuō),這是用來(lái)定義引用類(lèi)型的一種默認(rèn)模式
有其他OO語(yǔ)言經(jīng)驗(yàn)的開(kāi)發(fā)人員在看到獨(dú)立的構(gòu)造函數(shù)和原型時(shí),很可能會(huì)感到非常困惑。動(dòng)態(tài)原型模式正式致力于解決這個(gè)問(wèn)題的一個(gè)方案
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 寄生構(gòu)造函數(shù)模式if語(yǔ)句檢查的可以是初始化之后應(yīng)該存在的任何屬性和方法——不必用一大堆if語(yǔ)句檢查每個(gè)屬性和每個(gè)方法;只要檢查其中一個(gè)即可。對(duì)于采用這種模式的對(duì)象,還可以使用instanceof操作符確定它的類(lèi)型
通常,在前述的幾種模式都不適用的情況下,可以使用寄生構(gòu)造函數(shù)模式。這種模式的基本思想是創(chuàng)建一個(gè)函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對(duì)象的代碼,然后再返回新創(chuàng)建的對(duì)象;但從表面上看,這個(gè)函數(shù)又很像是典型的構(gòu)造函數(shù):
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"
構(gòu)造函數(shù)在不返回值的情況下,默認(rèn)會(huì)返回新對(duì)象實(shí)例。而通過(guò)在構(gòu)造函數(shù)的末尾添加一個(gè)return語(yǔ)句,可以重寫(xiě)調(diào)用構(gòu)造函數(shù)時(shí)返回的值。
這個(gè)模式可以在特殊的情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們想創(chuàng)建一個(gè)具有額外方法的特殊數(shù)組。由于不能直接修改Array構(gòu)造函數(shù),因此可以使用這個(gè)模式:
function SpecialArray() { //創(chuàng)建數(shù)組 var values = new Array(); //添加值 values.push.apply(values, arguments); //添加方法 values.toPipedString = function () { return this.join("|"); } //返回?cái)?shù)組 return values; } var colors = new SpecialArray("red", "blue", "green"); console.log(colors.toPipedString()); //"red|blue|green"
注意:返回的對(duì)象與構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型屬性直接沒(méi)有關(guān)系;也就是說(shuō),構(gòu)造函數(shù)返回的對(duì)象與在構(gòu)造函數(shù)外部創(chuàng)建的對(duì)象沒(méi)有什么不同。為此,不能依賴(lài)instanceof操作符來(lái)確定對(duì)象類(lèi)型。
2.7 穩(wěn)妥構(gòu)造函數(shù)模式由于存在上述問(wèn)題,建議在可以使用其他模式的情況下,不要使用這種模式。
所謂穩(wěn)妥對(duì)象,指的是沒(méi)有公共屬性,而且其方法也不引用this的對(duì)象。穩(wěn)妥對(duì)象最合適在一些安全的環(huán)境中(這些環(huán)境中會(huì)禁止使用this和new),或者在防止數(shù)據(jù)被其他應(yīng)用程序(如Mashup程序)改動(dòng)時(shí)使用。
function Person(name, age, job) { //創(chuàng)建要返回的對(duì)象 var o = new Object(); //可以在這里定義私有變量和函數(shù) //添加方法 o.sayName = function () { console.log(name); } return o; } var friend = Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
變量friend中保存的是一個(gè)穩(wěn)妥對(duì)象,而除了調(diào)用sayName()方法外,沒(méi)有別的方式可以訪問(wèn)其數(shù)據(jù)成員。即使有其他代碼會(huì)給這個(gè)對(duì)象添加方法或數(shù)據(jù)成員,但也不可能有別的辦法訪問(wèn)傳入到構(gòu)造函數(shù)中的原始數(shù)據(jù)。
3 繼承穩(wěn)妥構(gòu)造函數(shù)模式提供的這種安全性,使得它非常適合在某些安全執(zhí)行環(huán)境下使用——例如,ADsafe和Caja提供的環(huán)境
許多OO語(yǔ)言都支持兩種繼承方式:接口繼承和實(shí)現(xiàn)繼承。接口繼承只繼承方法簽名,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法。由于函數(shù)沒(méi)有簽名,在ECMAScript中無(wú)法實(shí)現(xiàn)接口繼承。ECMAScript只支持實(shí)現(xiàn)繼承,而且其實(shí)現(xiàn)繼承主要是依靠原型鏈來(lái)實(shí)現(xiàn)的
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 別忘記默認(rèn)的原型
所有引用類(lèi)型默認(rèn)都繼承了Object,而這個(gè)繼承也是通過(guò)原型鏈實(shí)現(xiàn)的。
instanceof:只要用這個(gè)操作符來(lái)測(cè)試實(shí)例與原型鏈中出現(xiàn)過(guò)的構(gòu)造函數(shù),結(jié)果就會(huì)返回true
console.log(instance instanceof Object); //true console.log(instance instanceof SuperType); //true console.log(instance instanceof SubType); //true
isPrototypeOf:只要是原型鏈中出現(xiàn)過(guò)的原型,都可以說(shuō)是該原型鏈所派生的實(shí)例的原型,因此該方法也會(huì)返回true
console.log(Object.prototype.isPrototypeOf(instance)); //true console.log(SuperType.prototype.isPrototypeOf(instance)); //true console.log(SubType.prototype.isPrototypeOf(instance)); //true3.1.3 謹(jǐn)慎地定義方法
子類(lèi)型有時(shí)候需要覆蓋超類(lèi)型中的某個(gè)方法,或者需要添加超類(lèi)型中不存在的某個(gè)方法。但不管怎樣,給原型添加方法的代碼一定要放在替換原型的語(yǔ)句之后
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; }; //重寫(xiě)超類(lèi)型中的方法 SubType.prototype.getSuperValue = function(){ return false; } var instance = new SubType(); console.log(instance.getSuperValue()); //false
getSuperValue()是原型鏈中已經(jīng)存在的一個(gè)方法,重寫(xiě)這個(gè)方法將會(huì)屏蔽原來(lái)的那個(gè)方法。當(dāng)通過(guò)SubType的實(shí)例調(diào)用getSuperValue()時(shí),調(diào)用的就是這個(gè)重新定義的方法;但通過(guò)SuperType的實(shí)例調(diào)用getSuperValue()時(shí),還會(huì)繼續(xù)調(diào)用原來(lái)的那個(gè)方法
在通過(guò)原型鏈屬性繼承時(shí),不能使用對(duì)象字面量創(chuàng)建原型方法。因?yàn)檫@樣就會(huì)重寫(xiě)原型鏈:
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType() { this.subProperty = false; } //繼承了SuperType SubType.prototype = new SuperType(); //使用字面量添加新方法,會(huì)導(dǎo)致上一行代碼無(wú)效 SubType.prototype = { //…… } var instance = new SubType(); console.log(instance.getSuperValue()); //error3.1.4 原型鏈的問(wèn)題
包含引用類(lèi)型值的原型會(huì)被所有實(shí)例共享。在通過(guò)原型來(lái)實(shí)現(xiàn)繼承時(shí),原型實(shí)際上會(huì)變成另一個(gè)類(lèi)型的實(shí)例。于是,原先的實(shí)例屬性也就變成了現(xiàn)在的原型屬性了
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的所有實(shí)例都會(huì)共享這一個(gè)colors屬性。
原型鏈的第二個(gè)問(wèn)題:在創(chuàng)建子類(lèi)型的實(shí)例時(shí),不能向超類(lèi)型的構(gòu)造函數(shù)中傳遞參數(shù)。實(shí)際上,應(yīng)該說(shuō)是沒(méi)有辦法在不影響所有對(duì)象實(shí)例的情況下,給超類(lèi)型的構(gòu)造函數(shù)傳遞參數(shù)。
3.2 借用構(gòu)造函數(shù)在子類(lèi)型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類(lèi)型構(gòu)造函數(shù)
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 傳遞參數(shù)
function SuperType(name){ this.name = name; } function SubType(){ //繼承了SuperType,同時(shí)還傳遞了參數(shù) SuperType.call(this, "Nicholas"); //實(shí)例屬性 this.age = 29; } var instance = new SubType(); console.log(instance.name); //"Nicholas"; console.log(instance.age); //29
3.3 組合繼承為了確保SuperType構(gòu)造函數(shù)不會(huì)重寫(xiě)子類(lèi)型的屬性,可以在調(diào)用超類(lèi)型構(gòu)造函數(shù)后,再添加應(yīng)該在子類(lèi)型中定義的屬性
組合繼承避免了原型鏈和借用構(gòu)造函數(shù)的缺陷,融合了它們的優(yōu)點(diǎn),成為JavaScript中最常用的繼承模式。而且,instanceof和isPrototypeOf()也能狗用于識(shí)別基于組合繼承創(chuàng)建的對(duì)象。
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 原型式繼承
原型式繼承并沒(méi)有使用嚴(yán)格意義上的構(gòu)造函數(shù)。他的想法是借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類(lèi)型
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()函數(shù)內(nèi)部,先創(chuàng)建了一個(gè)臨時(shí)性的構(gòu)造函數(shù),然后將傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回了這個(gè)臨時(shí)類(lèi)型的一個(gè)新實(shí)例。從本質(zhì)上講,object()隊(duì)傳入其中的對(duì)象執(zhí)行了一次淺復(fù)制
ECMAScript 5通過(guò)新增Object.create()方法規(guī)范化了原型式繼承:
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"]
在傳入一個(gè)參數(shù)的情況下,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()方法的第二個(gè)參數(shù)與Object.defineProperties()方法的第二個(gè)參數(shù)格式相同:每個(gè)屬性都是通過(guò)自己的描述符定義的。以這種方式指定的任何屬性都會(huì)覆蓋原型對(duì)象上的同名屬性
在沒(méi)有必要興師動(dòng)眾地創(chuàng)建構(gòu)造函數(shù),而只想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持類(lèi)似的情況下,原型試?yán)^承時(shí)完全可以勝任的。不過(guò)別忘了,包含引用類(lèi)型值的屬性始終都會(huì)共享相應(yīng)的值,就像使用原型模式一樣。
3.5 寄生式繼承寄生式繼承是與原型試?yán)^承緊密相關(guān)的一種思路。寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類(lèi)似,即創(chuàng)建一個(gè)僅用于封裝繼承過(guò)程的函數(shù),該函數(shù)在內(nèi)部以某種方式來(lái)增強(qiáng)對(duì)象,最后再像真的是它做了所有工作一樣返回對(duì)象。一下代碼規(guī)范了寄生式繼承模式:
function createAnother(original){ var clone = object(original); //通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象 clone.sayHi = function(){ //以某種方式來(lái)增強(qiáng)這個(gè)對(duì)象 console.log("hi"); }; return clone; //返回這個(gè)對(duì)象 }
可以像下面這樣來(lái)使用createAnother()函數(shù):
var person = { name : "Nicholas", friends : ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi"
新對(duì)象不僅具有person的所有屬性和方法,而且還有自己的sayHi()方法
在主要考慮對(duì)象而不是自定義類(lèi)型和構(gòu)造函數(shù)的情況下,寄生式繼承也是一種有用的模式。前面示范繼承模式時(shí)使用的object()函數(shù)不是必須的;任何能夠返回新對(duì)象的函數(shù)都適用于此模式
3.6 寄生組合式繼承通過(guò)借用構(gòu)造函數(shù)來(lái)繼承屬性,通過(guò)原型鏈的混成形式來(lái)繼承方法
寄生組合式繼承的基本模式如下:
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //創(chuàng)建對(duì)象 prototype.constructor = subType; //增強(qiáng)對(duì)象 subType.prototype = prototype; //指定對(duì)象 } 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); };
這個(gè)例子的高效率體現(xiàn)在它只調(diào)用了一次SuperType構(gòu)造函數(shù),并且因此避免了再SubType.prototype上面創(chuàng)建不必要的、多余的屬性。與此同時(shí),原型鏈還能保持不變;因此,還能夠正常使用instanceof和isPrototypeOf()。
開(kāi)發(fā)人員普遍認(rèn)為寄生組合式繼承時(shí)引用類(lèi)型最理想的繼承方式。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/82806.html
摘要:創(chuàng)建一個(gè)新對(duì)象將構(gòu)造函數(shù)的作用域賦給新對(duì)象因此就指向了這個(gè)新對(duì)象執(zhí)行構(gòu)造函數(shù)中的代碼為這個(gè)新對(duì)象添加屬性返回新對(duì)象。 本章內(nèi)容 理解對(duì)象屬性 理解并創(chuàng)建對(duì)象 理解繼承 ECMA-262把對(duì)象定義為:無(wú)序?qū)傩缘募希鋵傩钥梢园局怠?duì)象或者函數(shù) 理解對(duì)象 創(chuàng)建對(duì)象 創(chuàng)建自定義對(duì)象的最簡(jiǎn)單方式就是創(chuàng)建一個(gè)Object的實(shí)例,再為它添加屬性和方法。 var person = new...
摘要:高程讀書(shū)筆記第六章理解對(duì)象創(chuàng)建自定義對(duì)象的方式有創(chuàng)建一個(gè)實(shí)例,然后為它添加屬性和方法。創(chuàng)建了自定義的構(gòu)造函數(shù)之后,其原型對(duì)象默認(rèn)只會(huì)取得屬性至于其他方法都是從繼承而來(lái)的。 JS高程讀書(shū)筆記--第六章 理解對(duì)象 創(chuàng)建自定義對(duì)象的方式有創(chuàng)建一個(gè)Object實(shí)例,然后為它添加屬性和方法。還可用創(chuàng)建對(duì)象字面量的方式 屬性類(lèi)型 ECMAScript在定義只有內(nèi)部采用的特性時(shí),描述了屬性的各種特征...
摘要:高程第六章繼承理解與實(shí)踐昨日細(xì)細(xì)的讀了一遍高程現(xiàn)在寫(xiě)篇文章來(lái)鞏固下認(rèn)知吧讀首先是從中讀到了什么我自己也在讀書(shū)的時(shí)候用筆記下了各個(gè)部分的點(diǎn)現(xiàn)在等于閱讀筆記回憶下書(shū)本理解基礎(chǔ)第五版中規(guī)定了兩種屬性數(shù)據(jù)屬性訪問(wèn)器屬性數(shù)據(jù)屬性包含一個(gè)數(shù)據(jù)值的位 JavaScript高程第六章:繼承-理解與實(shí)踐昨日細(xì)細(xì)的讀了一遍JavaScript高程,現(xiàn)在寫(xiě)篇文章來(lái)鞏固下認(rèn)知吧. 讀 首先是從中讀到了什么,我...
摘要:把原型修改為另外一個(gè)對(duì)象就等于切斷了構(gòu)造函數(shù)與最初原型之間的聯(lián)系。組合使用構(gòu)造函數(shù)模式動(dòng)態(tài)原型模式通過(guò)檢查某個(gè)應(yīng)該存在的方法是否有效,來(lái)決定是否需要初始化原型。 理解對(duì)象 屬性類(lèi)型 數(shù)據(jù)屬性 數(shù)據(jù)屬性包含一個(gè)數(shù)據(jù)值的位置。在這個(gè)位置可以讀取和寫(xiě)入值。數(shù)據(jù)屬性有 4 個(gè)描述其行為的特性。 [[Configurable]] :表示能否通過(guò) delete 刪除屬性從而重新定義屬性,能否修...
摘要:三種使用構(gòu)造函數(shù)創(chuàng)建對(duì)象的方法和的作用都是在某個(gè)特殊對(duì)象的作用域中調(diào)用函數(shù)。這種方式還支持向構(gòu)造函數(shù)傳遞參數(shù)。叫法上把函數(shù)叫做構(gòu)造函數(shù),其他無(wú)區(qū)別適用情境可以在特殊的情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù)。 一、工廠模式 工廠模式:使用字面量和object構(gòu)造函數(shù)會(huì)有很多重復(fù)代碼,在此基礎(chǔ)上改進(jìn)showImg(https://segmentfault.com/img/bVbmKxb?w=456&...
閱讀 3170·2021-09-10 10:51
閱讀 3351·2021-08-31 09:38
閱讀 1639·2019-08-30 15:54
閱讀 3129·2019-08-29 17:22
閱讀 3214·2019-08-26 13:53
閱讀 1960·2019-08-26 11:59
閱讀 3283·2019-08-26 11:37
閱讀 3308·2019-08-26 10:47