前端(vue)入門到精通課程:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調(diào)試工具:點擊使用
面試官:“你說說 JavaScript 中實現(xiàn)繼承有哪幾種方法?”
緊張的萌新:“額,class 中用 extends 實現(xiàn)繼承,然后…沒了…”
面試官:“…”
······
想必絕大部分人一說繼承就會想到類中的繼承吧,但其實繼承可不是 class 的專利,本文將總結,JavaScript 中關于繼承的幾種方案,其中包括原型鏈,盜用構造函數(shù)、組合式等等,助你力壓面試官。
注意:本文比較適合具備一定 JS 進階基礎的同學(不會也沒關系,收藏就會了?),涉及知識點有:原型、原型鏈、構造函數(shù)、this指向等。如果文中有不對、疑惑的地方,歡迎在評論區(qū)留言指正?
0. 繼承
繼承是面向?qū)ο缶幊讨杏懻撟疃嗟脑掝}。很多面向?qū)ο笳Z言都支持兩種繼承:接口繼承和實現(xiàn)繼承。前者只繼承方法簽名,后者繼承實際的方法。 接口繼承在 ECMAScript中 是不可能的,因為函數(shù)沒有簽名。實現(xiàn)繼承是 ECMAScript 唯一支持的繼承方式,而這主要是通過原型鏈實現(xiàn)的。
1. 原型鏈繼承【方案一】
ECMA-262 把原型鏈定義為 ECMAScript 的主要繼承方式。其基本思想就是通過原型繼承多個引用類型的屬性和方法。重溫一下構造函數(shù)、原型和實例的關系:
- 每個構造函數(shù)都有一個
prototype
屬性指向原型對象 - 所有原型對象自動獲得一個名為
constructor
的屬性,指回與之關聯(lián)的構造函數(shù)
而實例有一個內(nèi)部指針指向原型。如果原型是另一個類型的實例呢?那就意味著這個原型本身有一個內(nèi)部指針指向另一個原型,相應的另一個原型也有一個指針指向另一個構造函數(shù)。這樣就在實例和原型之間構造了一條原型鏈。這就是原型鏈的基本構想。
實現(xiàn)原型鏈繼承涉及如下代碼模式
// 定義 Person 構造函數(shù) function Person() { this.name = 'CoderBin' } // 給 Person 的原型上添加 getPersonValue 方法(原型方法) Person.prototype.getPersonValue = function() { return this.name } // 定義 Student 構造函數(shù) function Student() { this.sno = '001' } // 繼承 Person — 將 Peson 的實例賦值給 Student 的原型 Student.prototype = new Person() Student.prototype.getStudentValue = function() { return this.sno } // 實例化 Student let stu = new Student() console.log(stu.getPersonValue()) // CoderBin
1.1 代碼解讀
以上代碼定義了兩個構造函數(shù):Person 和 Student。這兩個構造函數(shù)分別定義了一個屬性和一個方法。
這兩個類型的主要區(qū)別是 Student 通過創(chuàng)建 Person 的實例并將其賦值給自己的原型 Student.prototype
實現(xiàn)了對 Person 的繼承。
這個賦值重寫了 Student 最初的原型,將其替換為 Person 的實例。這意味著 Person 實例可以訪問的所有屬性和方法也會存在于Student.prototype
。這樣實現(xiàn)繼承之后,代碼緊接著又給Student.prototype
,也就是這個 Person 的實例添加了 一個新方法。最后又創(chuàng)建了 Student 的實例并調(diào)用了它繼承的getPersonValue()
方法。
下圖展示了子類的實例與兩個構造函數(shù)及其對應的原型之間的關系:
1.2 代碼核心解析
這個例子中實現(xiàn)繼承的關鍵,是 Student 沒有使用默認原型,而是將其替換成了一個新的對象。這個新的對象恰好是 Person 的實例。這樣一來,Student 的實例不僅能從 Person 的實例中繼承屬性和方法,而且還與 Person 的原型掛上了鉤。于是 stu(通過內(nèi)部的 [[Prototype]] )指向Student.prototype
,而Student.prototype
(作為 Person 的實例又通過內(nèi)部的 [[Prototype]] )指向Person.prototype
。
注意1:getPersonValue() 方法還在Person.prototype
對象上,而 name 屬性則在Student.prototype
上。這是因為 getPersonValue() 是一個原型方法,而 name 是一個實例屬性。Student.prototype
現(xiàn)在是 Person 的一個實例,因此 name 才會存儲在它上面。
注意2:由于 Student.prototype
的 constructor 屬性被重寫為指向 Person,所以 stu.constructor 也指向 Person 。
1.3 默認原型
實際上,原型鏈中還有一環(huán)。默認情況下,所有引用類型都繼承自 Object ,這也是通過原型鏈實現(xiàn)的。任何函數(shù)的默認原型都是一個 Object 的實例,這意味著這個實例有一個內(nèi)部指針指向Object.prototype
。這也是為什么自定義類型能夠繼承包括 toString() 、valueOf() 在內(nèi)的所有默認方法的原因。因此前面的例子還有額外一層繼承關系。
下圖展示了完整的原型鏈。
Student 繼承 Person ,而 Person 繼承 Object 。在調(diào)用 stu.toString() 時,實際上調(diào)用的是保存在 Object.prototype 上的方法。
1.4 原型與繼承的關系
原型與實例的關系可以通過兩種方式來確定。
1.4.1 instanceof
第一種方式是使用instanceof
操作符,如果一個實例的原型鏈中出現(xiàn)過相應的構造函數(shù),則instanceof
返回 true 。如下例所示:
console.log(stu instanceof Object) // true console.log(stu instanceof Person) // true console.log(stu instanceof Student) // true
從技術上講,stu 是 Object、Person 和 Student 的實例,因為 stu 的原型鏈中包含這些構造函數(shù)的原型。結果就是 instanceof
對所有這些構造函數(shù)都返回 true 。
1.4.2 isPrototypeOf()
確定這種關系的第二種方式是使用isPrototypeOf()
方法。原型鏈中的每個原型都可以調(diào)用這個方法,如下例所示,只要原型鏈中包含這個原型,這個方法就返回 true 。
console.log(Object.prototype.isPrototypeOf(stu)) // true console.log(Person.prototype.isPrototypeOf(stu)) // true console.log(Student.prototype.isPrototypeOf(stu)) // true
1.5 關于方法
子類有時候需要覆蓋父類的方法,或者增加父類沒有的方法。為此, 這些方法必須在原型賦值之后再添加到原型上。來看下面的例子:
// 定義 Person 構造函數(shù) function Person() { this.name = 'CoderBin' } // 給 Person 的原型上添加 getPersonValue 方法(原型方法) Person.prototype.getPersonValue = function() { return this.name } // 定義 Student 構造函數(shù) function Student() { this.sno = '001' } // 繼承 Person Student.prototype = new Person() // 新方法 —— 1 Student.prototype.getStudentValue = function() { return this.sno } // 覆蓋已有的方法 —— 2 Student.prototype.getPersonValue = function() { return 'Bin' } // 實例化 Student let stu = new Student() console.log(stu.getPersonValue()) // Bin
在上面的代碼中,注釋1、2的部分涉及兩個方法。
- 第一個方法 getStudentValue() 是 Student 的新方法,
- 第二個方法 getPersonValue() 是原型鏈上已經(jīng)存在但在這里被遮蔽的方法。
后面在 Student 實例上調(diào)用 getPersonValue() 時調(diào)用的是2這個方法。而 Person 的實例仍然會調(diào)用最初的方法。
重點一:上述兩個方法都是在把原型賦值為 Person 的實例之后定義的。
重點二:另一個要理解的重點是,以對象字面量方式創(chuàng)建原型方法會破壞之前的原型鏈,因為這相當于重寫了原型鏈。下面是一個例子:
// 定義 Person 構造函數(shù) function Person() { this.name = 'CoderBin' } // 給 Person 的原型上添加 getPersonValue 方法(原型方法) Person.prototype.getPersonValue = function() { return this.name } // 定義 Student 構造函數(shù) function Student() { this.sno = '001' } // 繼承 Person Student.prototype = new Person() // 通過對象字面量添加新方法,這會導致上一行無效!??! Student.prototype = { getStudentValue() { return this.sno }, someOtherMethod() { return 'something' } } // 實例化 Student let stu = new Student() console.log(stu.getPersonValue()) // TypeError: stu.getPersonValue is not a function
在這段代碼中,子類的原型在被賦值為 Person 的實例后,又被一個對象字面量覆蓋了。覆蓋后的原型是一個Object 的實例,而不再是 Person 的實例。因此之前的原型鏈就斷了。Student 和 Person 之間也沒有關系了。
1.6 原型鏈繼承的缺陷
原型鏈雖然是實現(xiàn)繼承的強大工具,但它也有問題。
主要問題出現(xiàn)在原型中包含引用值的時候。前面在談到原型的問題時也提到過,原型中包含的引用值會在所有實例間共享,這也是為什么屬性通常會在構造函數(shù)中定義而不會定義在原型上的原因。在使用原型實現(xiàn)繼承時,原型實際上變成了另一個類型的實例【1】。
這意味著原先的實例屬性搖身一變成為了原型屬性。下面的例子揭示了這個問題:
// 定義 Person 構造函數(shù) function Person() { this.letters = ['a', 'b', 'c'] } // 定義 Student 構造函數(shù) function Student() { this.sno = '001' } // 繼承 Person Student.prototype = new Person() let stu1 = new Student() let stu2 = new Student() stu1.letters.push('d') console.log(stu1.letters) // ['a', 'b', 'c', 'd'] console.log(stu2.letters) // ['a', 'b', 'c', 'd']
代碼解析: 在這個例子中,Person 構造函數(shù)定義了一個 letters 屬性,其中包含一個數(shù)組(引用值)。每個 Person 的實例都會有自己的 letters 屬性,包含自己的數(shù)組。但是,當 Student 通過原型繼承 Person 后,Student.prototype
變成了 Person 的一個實例,因而也獲得了自己的 letters 屬性。這類似于創(chuàng)建了Student.prototype.letters
屬性。最終結果是,Student 的所有實例都會共享這個 letters 屬性。這一點通過 stu1.letters 上的修改也能反映到 stu2.letters 上就可以看出來。
原型鏈的第二個問題是,子類型在實例化時不能給父類型的構造函數(shù)傳參【2】。
事實上,我們無法在不影響所有對象實例的情況下把參數(shù)傳進父類的構造函數(shù)。再加上之前提到的原型中包含引用值的問題,就導致原型鏈基本不會被單獨使用。
2. 盜用構造函數(shù)繼承【方案二】
為了解決原型包含引用值導致的繼承問題,一種叫作“盜用構造函數(shù)” (constructor stealing)的技術在開發(fā)社區(qū)流行起來(這種技術有時也稱作“對象偽裝”或“經(jīng)典繼承”)?;舅悸泛芎唵危?strong>在子類構造函數(shù)中調(diào)用父類構造函數(shù)。 因為畢竟函數(shù)就是在特定上下文中執(zhí)行代碼的簡單對象,所以可以使用apply()
和call()
方法以新創(chuàng)建的對象為上下文執(zhí) 行構造函數(shù)。來看下面的例子:
// 定義 Person 構造函數(shù) function Person() { this.letters = ['a', 'b', 'c'] } // 定義 Student 構造函數(shù) function Student() { // 繼承 Person — 使用 call() 方法調(diào)用 Person 構造函數(shù) Person.call(this) } let stu1 = new Student() let stu2 = new Student() stu1.letters.push('d') console.log(stu1.letters) // ['a', 'b', 'c', 'd'] console.log(stu2.letters) // ['a', 'b', 'c']
代碼解析: 示例中繼承 Person 那一行代碼展示了盜用構造函數(shù)的調(diào)用。通過使用call() (或 apply() )方法,Person 構造函數(shù)在為 Student 的實例創(chuàng)建的新對象的上下文中執(zhí)行了。這相當于新的 Student 對象上運行了 Person() 函數(shù)中的所有初始化代碼。結果就是每個實例都會有自己的 letters 屬性。
2.1 傳遞參數(shù)
相比于使用原型鏈,盜用構造函數(shù)的一個優(yōu)點就是可以在子類構造函數(shù)中向父類構造函數(shù)傳參。來看下面的例子:
// 定義 Person 構造函數(shù) function Person(name) { this.name = name } // 定義 Student 構造函數(shù) function Student(name) { // 繼承 Person Person.call(this, name) // 實例屬性 this.age = 18 } let stu = new Student('CoderBin') console.log(stu.name) // CoderBin console.log(stu.age) // 18
代碼解析:在這個例子中,Person 構造函數(shù)接收一個參數(shù) name ,然后將它賦值給一個屬性。在 Student 構造函數(shù)中調(diào)用 Person 構造函數(shù)時傳入這個參數(shù),實際上會在 Student 的實例上定義 name 屬性。為確保 Person 構造函數(shù)不會覆蓋 Student 定義的屬性,可以在調(diào)用父類構造函數(shù)之后再給子類實例添加額外的屬性。
2.2 盜用構造函數(shù)繼承的缺陷
盜用構造函數(shù)的主要缺點,也是使用構造函數(shù)模式自定義類型的問題:必須在構造函數(shù)中定義方法,因此函數(shù)不能重用。此外,子類也不能訪問父類原型上定義的方法,因此所有類型只能使用構造函數(shù)模式。由于存在這些問題,盜用構造函數(shù)基本上也不能單獨使用。
3. 組合繼承【方案三】
組合繼承 (有時候也叫偽經(jīng)典繼承)綜合了原型鏈和盜用構造函數(shù),將兩者的優(yōu)點集中了起來。基本的思路是:使用原型鏈繼承原型上的屬性和方法,而通過盜用構造函數(shù)繼承實例屬性。 這樣既可以把方法定義在原型上以實現(xiàn)重用,又可以讓每個實例都有自己的屬性。來看下面的例子:
// 定義 Person 構造函數(shù) function Person(name) { this.name = name this.letters = ['a', 'b', 'c'] } // 在 Person 的原型上添加 sayName 方法 Person.prototype.sayName = function() { console.log(this.name + ' 你好~') } // 定義 Student 構造函數(shù) function Student(name, age) { // 繼承屬性 Person.call(this, name) this.age = age } // 繼承方法 Student.prototype = new Person() // 在 Student 的原型上添加 sayAge 方法 Student.prototype.sayAge = function() { console.log(this.age) } let stu1 = new Student('CoderBin', 18) let stu2 = new Student('Bin', 23) stu1.letters.push('d') // 輸出 stu1 的信息 console.log(stu1.letters) // [ 'a', 'b', 'c', 'd' ] stu1.sayName() // CoderBin 你好~ stu1.sayAge() // 18 // 輸出 stu2 的信息 console.log(stu2.letters) // [ 'a', 'b', 'c'] stu2.sayName() // Bin 你好~ stu2.sayAge() // 23
代碼解析:在這個例子中,Person 構造函數(shù)定義了兩個屬性,name 和 letters ,而它的原型上也定義了一個方法叫 sayName() 。Student 構造函數(shù)調(diào)用了 Person 構造函數(shù),傳入了 name 參數(shù),然后又定義了自己的屬性 age 。
此外,Student.prototype
也被賦值為 Person 的實例。 原型賦值之后,又在這個原型上添加了新方法sayAge() 。這樣,就可以創(chuàng)建兩個 Student 實例,讓這兩個實例都有自己的屬性,包括 letters , 同時還共享相同的方法。
最后:組合繼承彌補了原型鏈和盜用構造函數(shù)的不足,是 JavaScript 中使用最多的繼承模式。而且組合繼承也保留了instanceof
操作符和isPrototypeOf()
方法識別合成對象的能力。
4. 原型式繼承【方案四】
2006年,Douglas Crockford(JSON之父) 寫了一篇文章:《JavaScript中的原型式繼承》(“Prototypal Inheritance in JavaScript”)。這篇文章介紹了 一種不涉及嚴格意義上構造函數(shù)的繼承方法。他的出發(fā)點是即使不自定義類型也可以通過原型實現(xiàn)對象之間的信息共享。文章最終給出了一個函數(shù):
function object(o) { function F() {} F.prototype = o return new F() }
這個object() 函數(shù)會創(chuàng)建一個臨時構造函數(shù),將傳入的對象賦值給這個構造函數(shù)的原型,然后返回這個臨時類型的一個實例。
4.1 方法一:object
本質(zhì)上,object() 是對傳入的對象執(zhí)行了一次淺復制。 來看下面的例子:
function object(o) { function F() {} F.prototype = o return new F() } let person = { name: 'CoderBin', letters: ['a', 'b', 'c'] } let p1 = object(person) let p2 = object(person) p1.name = 'p1' p1.letters.push('d') p2.name = 'p2' p2.letters.push('e') console.log(person.letters) // [ 'a', 'b', 'c', 'd', 'e' ]
代碼解析:在這個例子中,person 對象定義了另一個對象也應該共享的信息,把它傳給 object()
之后會返回一個新對象。這個新對象的原型是 person ,意味著它的原型上既有原始值屬性又有引用值屬性。這也意味著 person.letters 不僅是 person 的屬性,也會跟 p1 和 p2 共享。這里實際上克隆了兩個 person 。
Crockford推薦的原型式繼承適用于這種情況:你有一個對象,想在它的基礎上再創(chuàng)建一個新對象。你需要把這個對象先傳給 object()
,然后再對返回的對象進行適當修改。
4.2 方法二:Object.create()
ECMAScript5 通過增加Object.create()
方法將原型式繼承的概念規(guī)范化了。這個方法接收兩個參數(shù):作為新對象原型的對象,以及給新對象定義額外屬性的對象(第二個可選)。在只有一個參數(shù)時,Object.create()
與這里的object()
方法效果相同:
let person = { name: 'CoderBin', letters: ['a', 'b', 'c'] } let p1 = Object.create(person) let p2 = Object.create(person) p1.name = 'p1' p1.letters.push('d') p2.name = 'p2' p2.letters.push('e') console.log(person.letters) // [ 'a', 'b', 'c', 'd', 'e' ]
Object.create()
的第二個參數(shù)與Object.defineProperties()
的第二個參數(shù)一樣:每個新增屬性都通過各自的描述符來描述。以這種方式添加的屬性會遮蔽原型對象上的同名屬性。比如:
let person = { name: 'CoderBin', letters: ['a', 'b', 'c'] } let p1 = Object.create(person, { name: { value: 'CoderBin' } }) console.log(p1.name)
原型式繼承非常適合不需要單獨創(chuàng)建構造函數(shù),但仍然需要在對象間共享信息的場合。但要記住,屬性中包含的引用值始終會在相關對象間共享,跟使用原型模式是一樣的。
5. 寄生式繼承【方案五】
與原型式繼承比較接近的一種繼承方式是寄生式繼承 (parasitic inheritance),也是Crockford首倡的一種模式。寄生式繼承背后的思路類似于寄生構造函數(shù)和工廠模式:創(chuàng)建一個實現(xiàn)繼承的函數(shù),以某種方式增強對象,然后返回這個對象?;镜募纳^承模式如下:
function inheritPrototype(o) { let clone = Object.create(o) // 通過調(diào)用函數(shù)創(chuàng)建一個新對象 clone.sayHi = function() { // 以某種方式增強這個對象 console.log('Hi~') } return clone // 返回這個對象 }
代碼解析:在這段代碼中,inheritPrototype() 函數(shù)接收一個參數(shù),就是新對象的基準對象。這個對象 o 會被傳給Object.create()
函數(shù),然后將返回的新對象賦值給 clone 。接著給 clone 對象添加一個新方法 sayHi() 。最后返回這個對象??梢韵裣旅孢@樣使用 inheritPrototype() 函數(shù):
let person = { name: 'CoderBin', letters: ['a', 'b', 'c'] } let p1 = inheritPrototype(person) p1.sayHi() // Hi~
代碼解析:這個例子基于 person 對象返回了一個新對象。新返回的 p1 對象具有 person 的所有屬性和方法,還有一個新方法叫 sayHi() 。寄生式繼承同樣適合主要關注對象,而不在乎類型和構造函數(shù)的場景。Object.create()
函數(shù)不是寄生式繼承所必需的,任何返回新對象的函數(shù)都可以在這里使用。
注意: 通過寄生式繼承給對象添加函數(shù)會導致函數(shù)難以重用,與構造函數(shù)模式類似。
6. 寄生式組合繼承【方案六】
組合繼承其實也存在效率問題。最主要的效率問題就是父類構造函數(shù)始終會被調(diào)用兩次:一次在是創(chuàng)建子類原型時調(diào)用,另一次是在子類構造函數(shù)中調(diào)用。本質(zhì)上,子類原型最終是要包含超類對象的所有實例屬性,子類構造函數(shù)只要在執(zhí)行時重寫自己的原型就行了。
6.1 組合式繼承的缺陷
再來看一看這個組合繼承的例子:
// 定義 Person 構造函數(shù) function Person(name) { this.name = name this.letters = ['a', 'b', 'c'] } // 在 Person 的原型上添加 sayName 方法 Person.prototype.sayName = function() { console.log(this.name) } // 定義 Student 構造函數(shù) function Student(name, age) { Person.call(this, name) // 第一次調(diào)用 Person() this.age = age } Student.prototype = new Person() // 第二次調(diào)用 Person() // 讓 Student 的原型指回 Student Student.prototype.constructor = Student // 在 Student 的原型上添加 sayAge 方法 Student.prototype.sayAge = function() { console.log(this.age) } let stu = new Student('CoderBin', 18) console.log(stu) // 輸出:Student { name: 'CoderBin', letters: [ 'a', 'b', 'c' ], age: 18 } console.log(Student.prototype) // 輸出: // Person { // name: undefined, // letters: [ 'a', 'b', 'c' ], // constructor: [Function: Student], // sayAge: [Function (anonymous)] // }
代碼解析:代碼中注釋的部分是調(diào)用 Person 構造函數(shù)的地方。在上面的代碼執(zhí)行后,Student.prototype
上會有兩個屬性:name 和 letters 。它們都是 Person 的實例屬性,但現(xiàn)在成為了 Student 的原型屬性。在調(diào)用 Student 構造函數(shù)時,也會調(diào)用 Person 構造函數(shù),這一次會在新對象上創(chuàng)建實例屬性 name 和 letters 。這兩個實例屬性會遮蔽原型上同名的屬性。
所以,執(zhí)行完上面的代碼后,有兩組 name 和 letters 屬性:一組在實例上,另一組在 Student 的原型上。這是調(diào)用兩次 Person 構造函數(shù)的結果。
6.2 解決方法
寄生式組合繼承通過盜用構造函數(shù)繼承屬性,但使用混合式原型鏈繼承方法?;舅悸肥?strong>不通過調(diào)用父類構造函數(shù)給子類原型賦值,而是取得父類原型的一個副本。說到底就是使用寄生式繼承來繼承父類原型,然后將返回的新對象賦值給子類原型。寄生式組合繼承的基本模式如下所示:
function inheritPrototype(subType, superType) { let prototype = Object.create(superType.prototype) // 創(chuàng)建對象 prototype.constructor = subType // 增強對象 subType.prototype = prototype // 賦值對象 }
代碼解析:這個 inheritPrototype()
函數(shù)實現(xiàn)了寄生式組合繼承的核心邏輯。這個函數(shù)接收兩個參數(shù):子類構造函數(shù)和父類構造函數(shù)。在這個函數(shù)內(nèi)部,第一步是創(chuàng)建父類原型的一個副本。然后,給返回的prototype 對象設置 constructor 屬性,解決由于重寫原型導致默認 constructor 丟失的問題。最后將新創(chuàng)建的對象賦值給子類型的原型。如下例所示,調(diào)用 inheritPrototype()
就可以實現(xiàn)前面例子中的子類型原型賦值:
// 定義 Person 構造函數(shù) function Person(name) { this.name = name this.letters = ['a', 'b', 'c'] } // 在 Person 的原型上添加 sayName 方法 Person.prototype.sayName = function() { console.log(this.name) } // 定義 Student 構造函數(shù) function Student(name, age) { Person.call(this, name) this.age = age } // 調(diào)用 inheritPrototype() 函數(shù),傳入 子類構造函數(shù) 和 父類構造函數(shù) inheritPrototype(Student, Person) // 在 Person 的原型上添加 sayAge 方法 Student.prototype.sayAge = function() { console.log(this.age) } let stu = new Student('CoderBin', 18) console.log(stu) // 輸出:Student { name: 'CoderBin', letters: [ 'a', 'b', 'c' ], age: 18 } console.log(Student.prototype) // 輸出 // Person { // constructor: [Function: Student], // sayAge: [Function (anonymous)] // }
這里只調(diào)用了一次 Person 構造函數(shù),避免了Student.prototype
上不必要也用不到的屬性,因此可以說這個例子的效率更高。而且,原型鏈仍然保持不變,因此instanceof
操作符和isPrototypeOf()
方法正常有效。寄生式組合繼承可以算是引用類型繼承的最佳模式。
7. 寫到最后
到此為止,關于 JavaScript 中實現(xiàn)繼承的六種方法就全部總結完畢了,如果你能堅持看到這里,相信繼承這一塊的知識你已經(jīng)足夠掌握了。當然,JS 還有其他相當重要的知識點,比如 this 指向等等,可以點擊 一篇文章帶你搞懂 this 的四個綁定規(guī)則 ✍ 前往學習。
【推薦學習:javascript高級教程】