javascript
【JS面试向】深入原型链之class的继承
class 是如何實(shí)現(xiàn)繼承的?
我相信時至今日,大部分同學(xué)看完題目都能很快的寫出答案。
使用 ES 6 提供的類,能夠很快的實(shí)現(xiàn)繼承。
class Parent {constructor() {this.name = '爸爸';this.books = ['JAVA']}showName() {console.log(this.name)} }class Child extends Parent {constructor() {super();this.name = '兒子';this.school = 'high school';}showSchool() {console.log(this.school);} } 復(fù)制代碼但是,其實(shí) JavaScript 本身是沒有類這個概念的,class 只是一個語法糖,Parent 與 Child 本質(zhì)上還是函數(shù)。所以 class 提供的繼承是基于 JavaScript 的原型鏈實(shí)現(xiàn)的。那么如何通過原型鏈實(shí)現(xiàn)繼承呢?
ES5中的繼承
JavaScript 中繼承的本質(zhì)就是子類共享父類的原型對象。
ps:僅個人觀點(diǎn),有什么不妥請大家及時反駁。
類式繼承
類式繼承通過子類的原型對象指向父類的實(shí)例,完成繼承。
代碼
function Parent() {this.name = '爸爸';this.books = ['JAVA']; }Parent.prototype.showName = function() {console.log(this.name); }function Child() {this.name = '兒子';this.school = 'high school' }Child.prototype = new Parent(); // 子類的原型對象指向父類的實(shí)例,子類就能訪問 Parent 原型對象上的方法了Child.prototype.showSchool = function() {console.log(this.school); } 復(fù)制代碼驗(yàn)證
const c = new Child(); // 調(diào)用父類方法 c.showName(); // 兒子 // 調(diào)用子類方法 c.showSchool(); // high school 復(fù)制代碼不足
看起來類式繼承很完美的完成了工作,Child 的實(shí)例化的對象正常調(diào)用了父類的方法。但是我們看看 c 到底長什么樣子。
{name: '兒子',school: 'high school',__proto__: {books: ['JAVA'],name: '爸爸',showSchool: f (),__proto__: {showName: f(),constructor: f Parent()// ...}} } 復(fù)制代碼可以看出,類式繼承具有以下幾個問題:
- 父類的屬性只實(shí)例化了一次,如果所有實(shí)例共享 books 這個屬性,任意一個修改了實(shí)例修改了 books 的話,而其他實(shí)例不知情,這種情況是很危險的。由于信息不對稱,很容易出現(xiàn)bug。
- 對象 c 的 proto 沒有 constructor。
- 有兩個name變量,節(jié)約內(nèi)存,可以去掉一個。
寄生組合式繼承
- 通過每次調(diào)用父類,傳入this,使引用類型的值不共享。
- 創(chuàng)建一個中間對象,顯式的設(shè)置 constructor。
- 子類的原型對象指向這個中間對象,中間對象的__proto__指向父類的原型對象。
ps:提個問題,這里的中間對象其實(shí)可以不用創(chuàng)建,直接指定子類的原型對象的__proto__為父類的原型對象(Object.setPrototypeOf)是否可行?文末說下一我的看法。
代碼
function Parent() {this.name = '爸爸';this.books = ['JAVA']; }Parent.prototype.showName = function() {console.log(this.name); }function Child() {Parent.call(this); // 像不像 class 中的 super()this.name = '兒子';this.school = 'high school' }// Object.create 創(chuàng)建一個中間對象 // 中間對象的__proto__指向父類的原型對象 // 子類的原型對象指向這個中間對象 Child.prototype = Object.create(Parent.prototype, {// 顯示的指定 constructorconstructor: {value: Child,enumerable: false, // 不可遍歷writable: true, // 可改寫configurable: true} })Child.prototype.showSchool = function() {console.log(this.school); } 復(fù)制代碼驗(yàn)證
const c = new Child(); // 調(diào)用父類方法 c.showName(); // 兒子 // 調(diào)用子類方法 c.showSchool(); // high school 復(fù)制代碼可以看一下 c 的結(jié)構(gòu)
{name: "兒子", books: ['JAVA'], school: "high school",__proto__: {showSchool: ? (),constructor: ? Child(),__proto__: {showName: ? (),constructor: ? Parent(),// ...}} } 復(fù)制代碼上面提到的問題都得到了解決,有興趣的同學(xué)可以去對比一下與class實(shí)例化后的繼承的結(jié)構(gòu)是否有區(qū)別。
小結(jié)
ps: 上面的問題,我覺得是可行的。但是
由于現(xiàn)代 JavaScript 引擎優(yōu)化屬性訪問所帶來的特性的關(guān)系,更改對象的 [[Prototype]]在各個瀏覽器和 JavaScript 引擎上都是一個很慢的操作。其在更改繼承的性能上的影響是微妙而又廣泛的,這不僅僅限于 obj.proto = ... 語句上的時間花費(fèi),而且可能會延伸到任何代碼,那些可以訪問任何[[Prototype]]已被更改的對象的代碼。如果你關(guān)心性能,你應(yīng)該避免設(shè)置一個對象的 [[Prototype]]。相反,你應(yīng)該使用 Object.create()來創(chuàng)建帶有你想要的[[Prototype]]的新對象。 ——摘自 MDN
參考資料:
- 【JavaScript 設(shè)計模式】 —— 張容銘
- MDN
總結(jié)
以上是生活随笔為你收集整理的【JS面试向】深入原型链之class的继承的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 音乐播放类应用后台播放耗电评测报告
- 下一篇: Spring Cloud Alibaba