摘要:構(gòu)造函數(shù)本身也是函數(shù),只不過可以用來(lái)創(chuàng)建對(duì)象而已。在創(chuàng)建子類型的實(shí)例時(shí),沒有辦法在不影響所有對(duì)象實(shí)例的情況下,不能向超類型的構(gòu)造函數(shù)中傳遞參數(shù)。借用構(gòu)造函數(shù)又叫偽造對(duì)象或經(jīng)典繼承。
本章內(nèi)容
理解對(duì)象屬性
理解并創(chuàng)建對(duì)象
理解繼承
ECMA-262 把對(duì)象定義為:“無(wú)序?qū)傩缘募?,其屬性可以包含基本值、?duì)象或者函數(shù)?!眹?yán)格來(lái)講,這就相當(dāng)于說(shuō)對(duì)象是一組沒有特定順序的值。
每個(gè)對(duì)象都是基于一個(gè)引用類型創(chuàng)建的,既可以是原生類型,也可以是開發(fā)人員定義的類型。
6.1 理解對(duì)象創(chuàng)建對(duì)象最簡(jiǎn)單的方式就是創(chuàng)建一個(gè) Object 的實(shí)例,然后為它添加屬性和方法。
var person = { name: "Jack", age: 29, sayName: function() { alert(this.name); } }
這些屬性在創(chuàng)建時(shí)都帶有一些特征值(characteristic),JS 通過這些特征值來(lái)定義它們的行為。
6.1.1 屬性類型ECMAScript 中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性。
1. 數(shù)據(jù)屬性數(shù)據(jù)屬性包含一個(gè)數(shù)據(jù)值的位置。在這個(gè)位置可以讀取和寫入值。數(shù)據(jù)屬性有4個(gè)描述其行為的特性。
[[Configurable]]
[[Enumerable]]
[[Writable]]
[[Value]]
對(duì)于直接在對(duì)象上定義的屬性,它們的 [[Configurable]]、[[Enumerable]]、[[Writable]] 特性都被設(shè)置為 true,而 [[Value]] 特性被設(shè)置為指定的值。
要修改屬性默認(rèn)的特性,必須使用 ECMAScript 5 的 Object.defineProperty() 方法。接收3個(gè)參數(shù):屬性所在的對(duì)象、屬性名字、一個(gè)描述符對(duì)象。其中描述符(descriptor)對(duì)象的屬性必須是:configurable/enumerable/writable/value。設(shè)置其中的一個(gè)或多個(gè)值,可以修改對(duì)應(yīng)的特性值。
var person = {} Object.defineProperty(person, "name", { writable: false, configurable: false, value: "Nick" }) alert(person.name); // Nick person.name = Jack; alert(person.name); // Nick delete person.name; alert(person.name); // Nick
注意:一旦把屬性定義為不可配置的,就不能再把它變回可配置了。也就是說(shuō),可以多次調(diào)用 Object.defineProperty()方法修改同一個(gè)屬性,但在把 configurable 設(shè)置為 false 后,就不能了。
在調(diào)用 Object.defineProperty() 時(shí),如果不指定,則 configurable/writable/enumerable 都為 false。
2. 訪問器屬性訪問器屬性不包含數(shù)據(jù)值;它們包含一對(duì) getter/setter 函數(shù)(不過,這兩個(gè)函數(shù)都不是必須的)。在讀取訪問器屬性時(shí),會(huì)調(diào)用 getter 函數(shù),這個(gè)函數(shù)負(fù)責(zé)返回有效的值;在寫入訪問器屬性時(shí),會(huì)調(diào)用 setter 函數(shù)并傳入新值,這個(gè)函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù)。特性如下:
[[Configurable]]
[[[Enumerable]]
[[Get]]:在讀取屬性時(shí)調(diào)用的函數(shù),默認(rèn)值 undefined。
[[Set]]:在寫入屬性時(shí)調(diào)用的函數(shù),默認(rèn)值 undefined。
訪問器屬性不能直接定義,必須使用 Object.defineProperty() 。
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", {// IE9+ get: function() { return this._year; }, set: function(newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; alert(book.edition); // 2
下劃線是一種常用的記號(hào),用于表示只能通過對(duì)象方法來(lái)訪問的屬性。
以上是使用訪問器屬性的常見方式,即設(shè)置一個(gè)屬性的值會(huì)導(dǎo)致其他屬性的變化。
不一定要同時(shí)指定 getter 和 setter。只指定 getter 表示屬性是不能寫,反之則表示屬性不能讀。
Object.defineProperties() 可以通過描述符一次性定義多個(gè)屬性。接收2個(gè)參數(shù):1、第一個(gè)對(duì)象是要添加和修改其屬性的對(duì)象;2、第二個(gè)對(duì)象的屬性與第一個(gè)對(duì)象中要添加或修改的屬性一一對(duì)應(yīng)。
var book = {}; Object.defineProperties(book, { // IE9+ _year: { writable: true, value: 2004 }, edition: { writable: true, value: 1 }, year: { get: function() { return this._year; }, set: function(newValue) { this._year = newValue; this.edition++; } } })6.1.3 讀取屬性的特性
使用 ECMAScript 5 中的 Object.getOwnPropertyDescriptor() IE9+ 方法,可以取得給定屬性的描述符。這個(gè)方法接收兩個(gè)參數(shù):1、屬性所在的對(duì)象;2、要讀取器描述符的屬性名稱。返回值是一個(gè)對(duì)象,如果是數(shù)據(jù)屬性,這個(gè)對(duì)象的屬性有 configurable/enumerable/writable/value,如果是訪問器屬性,則這個(gè)對(duì)象的屬性有 configurable/enumerable/get/set
// 使用前面的例子 var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); alert(descriptor.value); // 2004 alert(descriptor.configurable); //false alert(typeof descriptor.get); //"undefined" var descriptor = Object.getOwnPropertyDescriptor(book, "year"); alert(descriptor.value); //undefined alert(descriptor.enumerable); //false alert(typeof descriptor.get); //"function"6.2 創(chuàng)建對(duì)象
問題:使用同一個(gè)接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼。
6.2.1 工廠模式工廠模式抽象了創(chuàng)建具體對(duì)象的過程。用函數(shù)來(lái)封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié)。
function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; o.sayName = function() { alert(this.name); }; return o; } var person1 = createPerson("Jack", 29); var person2 = createPerson("Nick", 22);
工廠模式雖然解決了創(chuàng)建多個(gè)相似對(duì)象的問題,但是沒有解決對(duì)象識(shí)別的問題(即怎樣知道一個(gè)對(duì)象的類型)。
6.2.2 構(gòu)造函數(shù)模式可以創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對(duì)象類型的屬性和方法。
function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { alert(this.name); } } var person1 = new Person("Jack", 23); var person2 = new Person("Nick", 22);
構(gòu)造函數(shù)模式有以下幾個(gè)特點(diǎn):
沒有顯示地創(chuàng)建對(duì)象;
直接將屬性和方法賦給了this對(duì)象;
沒有return語(yǔ)句。
函數(shù)名開頭必須大寫。
構(gòu)造函數(shù)本身也是函數(shù),只不過可以用來(lái)創(chuàng)建對(duì)象而已。
要?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ì)象。
使用 instanceof 檢測(cè)對(duì)象類型:
alert(person1 instanceof Object); // true alert(person1 instanceof Person); // true alert(person2 instanceof Object); // true alert(person2 instanceof Person); // true
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來(lái)可以將它的實(shí)例標(biāo)識(shí)為一種特定的類型;而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方。
1. 將構(gòu)造函數(shù)當(dāng)做函數(shù)// 當(dāng)做構(gòu)造函數(shù)來(lái)使用 var person = new Person("Nick", 29); person.sayName(); // "Nick" // 當(dāng)做普通函數(shù)調(diào)用 Person("Nick", 29); // 添加到 window 對(duì)象 window.sayName(); // "Nick" // 在另一個(gè)對(duì)象作用域中調(diào)用 var o = new Object(); Person.call(o, "Nick", 29); o.sayName(); // "Nick"2. 構(gòu)造函數(shù)的問題
每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。在前面的例子中,person1 和 person2 的 sayName() 方法并不是同一個(gè) Function 的實(shí)例。因?yàn)楹瘮?shù)是對(duì)象,所以每定義一個(gè)函數(shù),也就實(shí)例化了一個(gè)對(duì)象。(new Function())。
解決的辦法,可以把函數(shù)定義移到構(gòu)造函數(shù)外部。
function Person(name, age) { this.name = name; this.age = age; this.sayName = sayName; } function sayName() { alert(this.name); } var person1 = new Person("Jack", 23); var person2 = new Person("Nick", 22);
但新問題是:在全局作用域定義的函數(shù)實(shí)際上只能被某個(gè)對(duì)象調(diào)用,這讓全局作用域名不副實(shí)。而且,如果對(duì)象需要定義很多方法,那么就要定義多個(gè)全局函數(shù),于是這個(gè)自定義的引用類型就沒有絲毫封裝性可言。
6.2.3 原型模式每個(gè)函數(shù)都有一個(gè) prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象,這個(gè)對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法。
也可以說(shuō) prototype 就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的對(duì)象實(shí)例的原型對(duì)象。使用原型對(duì)象的好處是可以讓所有“對(duì)象實(shí)例”共享“原型對(duì)象”所包含的屬性和方法。
6. 原型對(duì)象的問題它省略了為構(gòu)造函數(shù)傳遞初始化參數(shù)這一環(huán)節(jié),結(jié)果所有實(shí)例在默認(rèn)情況下都將取得相同的屬性值。
原型模式的最大問題是它的共享的本性所導(dǎo)致的。這個(gè)問題在包含引用類型值的屬性上顯而易見。
function Person() {} Person.prototype = { constructor: Person, friends: ["Jack"] }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Nick"); alert(person1.friends); // "Jack, Nick" alert(person2.friends); // "Jack, Nick" alert(person1.friends === person2.friends); // true
實(shí)例一般都是要有自己的全部屬性的,然而由于 person1.friends 和 person2.friends 都指向同一個(gè)數(shù)組,導(dǎo)致修改其中一個(gè),就會(huì)在另一個(gè)上同步共享。
6.2.4 組合使用構(gòu)造函數(shù)模式和原型模式構(gòu)造函數(shù)模式用于定義實(shí)例屬性,原型模式用于定義方法和共享的屬性。
每個(gè)實(shí)例都會(huì)擁有自己的一份實(shí)例屬性的副本,但同時(shí)又共享著對(duì)“方法”的引用,最大限度地節(jié)約了內(nèi)存。
這種混成模式還支持向構(gòu)造函數(shù)傳遞參數(shù)。
function Person(name, age) { this.name = name; this.age = age; this.friends = ["Jack"]; } Person.prototype = { constructor: Person, sayName: function() { alert(this.name); } } var person1 = new Person("Nick", 22); var person2 = new Person("Mike", 21); person1.friends.push("Jane"); alert(person1.friends); // "Jack, Jane" alert(person2.friends); // "Jack" alert(person1.friends === person2.friends); // false alert(person1.sayName === person2.sayName); // true
混成模式中,不同實(shí)例引用了不同的數(shù)組,因此原型對(duì)象的問題解決了。
6.2.5 動(dòng)態(tài)原型模式function Person(name, age) { // 屬性 this.name = name; this.age = age; // 方法 if (typeof this.sayName != "function") { Person.prototype.sayName: function() { alert(this.name); } } } var person1 = new Person("Nick", 22); person1.sayName();
if 語(yǔ)句檢查的可以是初始化之后應(yīng)該存在的任何屬性或方法——不必用一大堆 if 語(yǔ)句檢查每個(gè)屬性和每個(gè)方法,只要檢查其中一個(gè)即可。
對(duì)于采用這種模式創(chuàng)建的對(duì)象,可以使用 instanceof 操作符確定它的類型。
使用動(dòng)態(tài)原型模式時(shí),不能使用對(duì)象字面量重寫原型,如果重寫,則會(huì)切斷現(xiàn)有實(shí)例與新原型之間的聯(lián)系。
6.2.6 寄生構(gòu)造函數(shù)模式 6.2.7 穩(wěn)妥構(gòu)造函數(shù)模式 6.3 繼承由于函數(shù)沒有簽名,在ECMAScript 中無(wú)法實(shí)現(xiàn)【接口繼承】。ECMAScript 只支持【實(shí)現(xiàn)繼承】,而且其實(shí)現(xiàn)繼承主要依靠【原型鏈】來(lái)實(shí)現(xiàn)。
6.3.1 原型鏈基本思想
利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。
構(gòu)造函數(shù)、原型、實(shí)例之間的關(guān)系:
每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象;
原型對(duì)象都有一個(gè)指向構(gòu)造函數(shù)的指針;
實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針[[Prototype]]
實(shí)現(xiàn)原型鏈的基本模式:
function A() { this.aproperty = true; } A.prototype.getAValue = function() { return this.property; }; function B() { this.bproperty = false; } // 繼承了 A,創(chuàng)建了 B 的實(shí)例,并將實(shí)例賦給 B.prototype B.prototype = new A(); B.prototype.getBValue = function() { return this.bproperty; } var instance = new B(); alert(instance.getAValue); // true
實(shí)現(xiàn)的本質(zhì)是重寫原型對(duì)象,代之以一個(gè)新實(shí)例的類型。原來(lái)存在于 A 的實(shí)例中的所有屬性和方法,現(xiàn)在也存在于 B.prototype 中。
1. 默認(rèn)原型所有應(yīng)用類型默認(rèn)都繼承了 Object,而這個(gè)繼承也是通過原型鏈實(shí)現(xiàn)的。所有函數(shù)的默認(rèn)原型都是 Object 的實(shí)例,因此默認(rèn)原型都會(huì)包含一個(gè)內(nèi)部指針,指向 Object.prototype。這也是所有自定義類型都會(huì)繼承 toString()valueOf() 等默認(rèn)方法的根本原因。
2. 確定原型和實(shí)例的關(guān)系可以通過兩種方式來(lái)確定原型和實(shí)例之間的關(guān)系。
方法一:instanceof,只要用這個(gè)操作符來(lái)測(cè)試實(shí)例和原型鏈中出現(xiàn)過的構(gòu)造函數(shù),結(jié)果就會(huì)返回 true。
alert(instance instanceof Object); // true alert(instance instanceof A); // true alert(isntance instanceof B); // true
由于原型鏈的關(guān)系,instance 是 Object、A、B 中任何一個(gè)類型的實(shí)例。
方法二:isPropertyOf,只要是原型鏈中出現(xiàn)過的原型,都可以說(shuō)是該原型鏈所派生的實(shí)例的原型,因此該方法也會(huì)返回 true。
alert(Object.prototype.isPropertyOf(instance)); // true3. 謹(jǐn)慎地定義方法
子類型有時(shí)候需要覆蓋超類型中的某個(gè)方法,或者需要添加超類型中不存在的某個(gè)方法。給原型添加方法的代碼一定要放在替換原型的語(yǔ)句之后。
function A() { this.property = true; } A.prototype.getAValue = function() { return this.property; }; function B() { this.bproperty = false; } // 繼承了 A B.prototype = new A(); // 添加新方法 B.prototype.getBValue = function() { return this.bproperty; } // 重寫超類型方法 B.prototype.getAValue = function() { return false; }
注意,通過 A 的實(shí)例調(diào)用 getAValue() 方法時(shí),仍然繼續(xù)調(diào)用原來(lái)的方法。
在通過原型鏈實(shí)現(xiàn)繼承時(shí),不能使用對(duì)象字面量創(chuàng)建原型方法。因?yàn)檫@樣做會(huì)重寫原型鏈。
function A() { this.property = true; } A.prototype.getAValue = function() { return this.property; }; function B() { this.bproperty = false; } // 繼承了 A B.prototype = new A(); // 添加新方法 B.prototype = { getBValue: function() { return this.bproperty; } }; var instance = new B(); alert(instance.getAValue); // error!4. 原型鏈的問題
最主要的問題來(lái)自包含引用類型值的原型。包含引用類型值的原型屬性會(huì)被所有實(shí)例共享;而這也正是為什么要在構(gòu)造函數(shù)中,而不是在原型對(duì)象中定義屬性的原因。在通過原型來(lái)實(shí)現(xiàn)繼承時(shí),原型實(shí)際上會(huì)變成另一個(gè)類型的實(shí)例。于是,原先的實(shí)例屬性也就順理成章地變成了現(xiàn)在的原型屬性了。
在創(chuàng)建子類型的實(shí)例時(shí),沒有辦法在不影響所有對(duì)象實(shí)例的情況下,不能向超類型的構(gòu)造函數(shù)中傳遞參數(shù)。因此,實(shí)踐中很少會(huì)多帶帶使用原型鏈。
6.3.2 借用構(gòu)造函數(shù)(constructor stealing)又叫“偽造對(duì)象”或“經(jīng)典繼承”。
基本思想
在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)。函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對(duì)象,因此通過使用 apply() 和 call() 也可以在(將來(lái))新創(chuàng)建的對(duì)象上執(zhí)行構(gòu)造函數(shù)。
function A() { this.colors = ["red"]; } function B() { // 繼承了 A A.call(this); } var instance1 = new B(); instance1.colors.push("blue"); alert(instance1.colors); // "red, blue" var instance2 = new B(); alert(instance2.colors); // "red"1. 傳遞參數(shù)
相對(duì)于原型鏈而言,借用構(gòu)造函數(shù)有一個(gè)很大的有時(shí),可以在子類型構(gòu)造函數(shù)中向超類型構(gòu)造函數(shù)傳遞參數(shù)。
function A(name) { this.name = name; } function B() { // 繼承了 A A.call(this, "Jack"); } var instance1 = new B(); alert(instance1.name); // "Jack"
為了確保 A 構(gòu)造函數(shù)不會(huì)重寫子類型的屬性,可以在調(diào)用超類型構(gòu)造函數(shù)后,再添加應(yīng)該在子類型中定義的屬性。
2. 借用構(gòu)造函數(shù)的問題方法都在構(gòu)造函數(shù)中定義,因此函數(shù)復(fù)用就無(wú)從談起;
在超類型的原型中定義的方法,對(duì)子類型而言也是不可見的,結(jié)果所有類型都只能使用構(gòu)造函數(shù)模式。因此借用構(gòu)造函數(shù)也很少多帶帶使用。
6.3.3 組合繼承(combination inheritance)又叫“偽經(jīng)典繼承”,組合了原型鏈繼承和借用構(gòu)造函數(shù)繼承。既通過在原型上定義方法實(shí)現(xiàn)了函數(shù)服用,又能保證每個(gè)實(shí)例都擁有自己的屬性。
function A(name) { this.name = name; this.colors = ["red"]; } A.prototype.sayName = function() { alert(this.name); }; function B(name, age) { // 繼承屬性 A.call(this, name); // 第二次調(diào)用 A this.age = age; } // 繼承方法 B.prototype = new A(); // 第一次調(diào)用 A B.prototype.constructor = B; B.prototype.sayAge = function() { alert(this.age); } var instance1 = new B("Jack", 22); instance1.colors.push("blue"); alert(instance1.colors); // "red, blue" instance1.sayName(); // "Jack" instance1.sayAge(); // 22 var instance2 = new B("Nick", 21); alert(instance2.colors); // "red" instance2.sayName(); // "Nick" instance2.sayAge(); // 21
組合繼承避免了原型鏈和借用構(gòu)造函數(shù)的缺陷,融合了它們的優(yōu)點(diǎn),成為JS中最常用的繼承模式。而且,instanceof 和 isPropertyOf() 也能夠用于識(shí)別基于組合繼承創(chuàng)建的對(duì)象。
組合模式的問題
無(wú)論什么情況下,都會(huì)調(diào)用兩次超類型構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時(shí)候,一次是在子類型構(gòu)造函數(shù)內(nèi)部。
6.3.4 原型式繼承function object(o) { function F(){}; F.prototype = o; return new F(); }
原型式繼承要求必須有一個(gè)對(duì)象作為另一個(gè)對(duì)象的基礎(chǔ)。
ECMAScript 5 中新增了 Object.create() 來(lái)規(guī)范原型式繼承。接收2個(gè)參數(shù):1、一個(gè)用做新對(duì)象原型的對(duì)象;2、(可選)一個(gè)為新對(duì)象定義額外屬性的對(duì)象。在傳入一個(gè)參數(shù)的情況下,Object.create() 和 object() 的行為相同。
var person = {}; var anotherPerson = Object.create(person);
如果只想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持類似的情況下,原型式繼承完全可以勝任。但是包含引用類型值的屬性始終都會(huì)共享相應(yīng)的值,這點(diǎn)與原型模式一樣。
6.3.5 寄生式繼承(parasitic)它的思路與寄生構(gòu)造函數(shù)和工廠模式相似,即創(chuàng)建一個(gè)僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部以某種方式來(lái)增強(qiáng)對(duì)象,最后再像真地是它做了所有工作一樣返回對(duì)象。
function createAnother(original) { var clone = object(original); // 通過調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象 clone.sayHi = function() { // 以某種方式增強(qiáng)對(duì)象 alert("Hi"); }; return clone; // 返回對(duì)象 } var person = { name: "Jack", friends: ["Nick", "Tony"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); // "Hi"
新對(duì)象不僅具有 person 的所有屬性和方法,還有自己的方法。
在主要考慮“對(duì)象”而不是“自定義類型”和“構(gòu)造函數(shù)”的情況下,寄生式繼承也是一種有用的模式。object() 并不是必需的;任何能夠返回新對(duì)象的函數(shù)都適用于該模式。
注意:使用寄生式繼承來(lái)為對(duì)象添加函數(shù),會(huì)由于不能做到函數(shù)復(fù)用而降低效率;這一點(diǎn)與構(gòu)造函數(shù)模式類似。
6.3.6 寄生組合式繼承本質(zhì)上,就是使用“寄生式繼承”來(lái)繼承超類型的原型,再將結(jié)果指定給子類型的原型。
function inheritPrototype(sub, super) { var prototype = Object(super); // 創(chuàng)建對(duì)象 prototype.constructor = sub; // 增強(qiáng)對(duì)象 sub.prototype = prototype; // 指定對(duì)象 }
創(chuàng)建超類型原型的一個(gè)副本;
為創(chuàng)建的副本添加 constructor 屬性,從而彌補(bǔ)因【重寫原型】而失去的默認(rèn)的 constructor 屬性;
將新創(chuàng)建的對(duì)象(即副本)賦值給子類型的原型。
修改之前的例子:
function A(name) { this.name = name; } A.prototype.sayName = function() { alert(this.name); }; function B(age) { A.call(this, "Jack"); this.age = age; } inheritPrototype(B,A); B.prototype.sayAge = function() { alert(this.age); }
該模式的高效率體現(xiàn)在它只調(diào)用了一次 A 構(gòu)造函數(shù),并且因此避免了在 B 的 prototype 上面創(chuàng)建不必要的、多余的屬性。與此同時(shí),原型鏈還能保持不變;因此,還能夠正常使用 instanceof 和 isPrototypeOf() 方法。開發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類型最理想的繼承范式。
6.4 小結(jié)ECMAScript 支持面向?qū)ο螅∣O)變成,但不使用類或者接口。對(duì)象可以在代碼執(zhí)行過程中創(chuàng)建和增強(qiáng),因此具有動(dòng)態(tài)性而非嚴(yán)格定義的實(shí)體。在沒有類的情況下,可以采用下列模式創(chuàng)建對(duì)象:
工廠模式,使用簡(jiǎn)單的函數(shù)創(chuàng)建對(duì)象,為對(duì)象添加屬性和方法,然后返回對(duì)象。這個(gè)模式后來(lái)被構(gòu)造函數(shù)所取代。
構(gòu)造函數(shù)模式,可以創(chuàng)建自定義引用類型,可以像創(chuàng)建內(nèi)置對(duì)象實(shí)例一樣使用 new 操作符。不過,構(gòu)造函數(shù)模式的缺點(diǎn)是:它的每個(gè)成員都無(wú)法得到復(fù)用,包括函數(shù)。由于函數(shù)可以不局限于任何對(duì)象(即與對(duì)象具有松散耦合的特點(diǎn)),因此沒有理由不在多個(gè)對(duì)象間共享函數(shù)。
原型模式,使用構(gòu)造函數(shù)的 prototype 屬性來(lái)指定那些應(yīng)該共享的屬性和方法。組合使用構(gòu)造函數(shù)模式和原型模式時(shí),使用構(gòu)造函數(shù)定義實(shí)例屬性,使用原型模式定義共享的屬性和方法。
JS 主要通過原型鏈實(shí)現(xiàn)繼承。原型鏈的構(gòu)建是通過將一個(gè)類型的實(shí)例賦值給另外一個(gè)構(gòu)造函數(shù)的原型實(shí)現(xiàn)的。這樣,子類型就可以繼承超類型的屬性和方法,這一點(diǎn)與基于類的繼承很相似。
原型鏈的問題是:對(duì)象實(shí)例共享所有繼承的屬性和方法,因此不適宜多帶帶使用。解決這個(gè)問題的技術(shù)是借用構(gòu)造函數(shù),即在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)。這樣就可以做到每個(gè)實(shí)例都具有自己的屬性,同時(shí)還能保證只使用構(gòu)造函數(shù)模式來(lái)定義類型。
使用最多的繼承模式是組合繼承,這種模式使用原型鏈繼承共享的屬性和方法,通過借用構(gòu)造函數(shù)繼承實(shí)例屬性。
此外,還存在下列可供選擇的繼承模式:
原型式繼承,可以在不必預(yù)先定義構(gòu)造函數(shù)的情況下實(shí)現(xiàn)繼承,其本質(zhì)是執(zhí)行對(duì)給定對(duì)象的淺復(fù)制。而復(fù)制得到的副本還可以得到進(jìn)一步改造。
寄生式繼承,與原型式繼承非常相似,也是基于某個(gè)對(duì)象或某些信息創(chuàng)建一個(gè)對(duì)象,然后增強(qiáng)該對(duì)象,最后返回對(duì)象。為了解決組合繼承模式由于多次調(diào)用超類型構(gòu)造函數(shù)而導(dǎo)致的低效率問題,可以將這個(gè)模式與組合繼承模式一起使用。
寄生組合式繼承,集寄生式繼承與組合繼承的優(yōu)點(diǎn)于一身,是實(shí)現(xiàn)基于類型繼承的最有效方式。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106761.html
摘要:其中,描述符對(duì)象的屬性必須是和。吧設(shè)置為,表示不能從對(duì)象中刪除屬性。這個(gè)方法接收兩個(gè)對(duì)象參數(shù)要添加和修改其屬性值的對(duì)象,第二個(gè)是與第一個(gè)對(duì)象中要添加和修改的屬性值一一對(duì)應(yīng)。 理解對(duì)象 1、創(chuàng)建自定義對(duì)象的兩種方法: (1)創(chuàng)建一個(gè)Object實(shí)例,然后再為它添加屬性和方法。 var person = new Object(); person.name = Nicholas; ...
摘要:然而事實(shí)上并不是。函數(shù)本身也是一個(gè)對(duì)象,但是給這個(gè)對(duì)象添加屬性并不能影響。一圖勝千言作者給出的解決方案,沒有麻煩的,沒有虛偽的,沒有混淆視線的,原型鏈連接不再赤裸裸。所以是這樣的一個(gè)函數(shù)以為構(gòu)造函數(shù),為原型。 注意:本文章是個(gè)人《You Don’t Know JS》的讀書筆記。在看backbone源碼的時(shí)候看到這么一小段,看上去很小,其實(shí)忽略了也沒有太大理解的問題。但是不知道為什么,我...
閱讀 2749·2023-04-25 22:15
閱讀 1815·2021-11-19 09:40
閱讀 2160·2021-09-30 09:48
閱讀 3235·2021-09-03 10:36
閱讀 2036·2021-08-30 09:48
閱讀 1871·2021-08-24 10:00
閱讀 2739·2019-08-30 15:54
閱讀 713·2019-08-30 15:54