手把手教你如何实现继承
本文將從最簡(jiǎn)單的例子開始,從零講解在 JavaScript 中如何實(shí)現(xiàn)繼承。
小例子
現(xiàn)在有個(gè)需求,需要實(shí)現(xiàn) Cat 繼承 Animal ,構(gòu)造函數(shù)如下:
function Animal(name){this.name = name }function Cat(name){this.name = name } 復(fù)制代碼注:如對(duì)繼承相關(guān)的 prototype、constructor、__proto__、new 等內(nèi)容不太熟悉,可以先查看這篇文章:理性分析 JavaScript 中的原型
繼承
在實(shí)現(xiàn)這個(gè)需求之前,我們先談?wù)劺^承的意義。繼承本質(zhì)上為了提高代碼的復(fù)用性。
對(duì)于 JavaScript 來說,繼承有兩個(gè)要點(diǎn):
下面的內(nèi)容將圍繞這兩個(gè)要點(diǎn)展開。
第一版代碼
復(fù)用父構(gòu)造函數(shù)中的代碼,我們可以考慮調(diào)用父構(gòu)造函數(shù)并將 this 綁定到子構(gòu)造函數(shù)。
復(fù)用父原型中的代碼,我們只需改變?cè)玩溂纯?。將子?gòu)造函數(shù)的原型對(duì)象的 __proto__ 屬性指向父構(gòu)造函數(shù)的原型對(duì)象。
第一版代碼如下:
function Animal(name){this.name = name }function Cat(name){Animal.call(this,name) }Cat.prototype.__proto__ = Animal.prototype 復(fù)制代碼檢驗(yàn)一下是否繼承成功:我們?cè)?Animal 的原型對(duì)象上添加 eat 函數(shù)。使用 Cat 構(gòu)造函數(shù)生成一個(gè)名為 'Tom' 的實(shí)例對(duì)象 cat 。代碼如下:
function Animal(name){this.name = name }function Cat(name){Animal.call(this,name) }Cat.prototype.__proto__ = Animal.prototype// 添加 eat 函數(shù) Animal.prototype.eat = function(){console.log('eat') }var cat = new Cat('Tom') // 查看 name 屬性是否成功掛載到 cat 對(duì)象上 console.log(cat.name) // Tom // 查看是否能訪問到 eat 函數(shù) cat.eat() // eat // 查看 Animal.prototype 是否位于原型鏈上 console.log(cat instanceof Animal) // true // 查看 Cat.prototype 是否位于原型鏈上 console.log(cat instanceof Cat) //true 復(fù)制代碼經(jīng)檢驗(yàn),成功復(fù)用父構(gòu)造函數(shù)中的代碼,并復(fù)用父原型對(duì)象中的代碼,原型鏈正常。
圖示
弊端
__proto__ 屬性雖然可以很方便地改變?cè)玩?#xff0c;但是 __proto__ 直到 ES6 才添加到規(guī)范中,存在兼容性問題,并且直接使用 __proto__ 來改變?cè)玩湻浅O男阅?。所?__proto__ 屬性來實(shí)現(xiàn)繼承并不可取。
第二版代碼
針對(duì) __proto__ 屬性的弊端,我們考慮使用 new 操作符來替代直接使用 __proto__ 屬性來改變?cè)玩湣?/p>
我們知道實(shí)例對(duì)象中的 __proto__ 屬性指向構(gòu)造函數(shù)的 prototype 屬性的。這樣我們 Animal 的實(shí)例對(duì)象賦值給 Cat.prototype 。不就也實(shí)現(xiàn)了Cat.prototype.__proto__ = Animal.prototype 語句的功能了嗎?
代碼如下:
function Animal(name){this.name = name }function Cat(name){Animal.call(this,name) }Cat.prototype = new Animal() Cat.prototype.constructor = Cat 復(fù)制代碼使用這套方案有個(gè)問題,就是在將實(shí)例對(duì)象賦值給 Cat.prototype 的時(shí)候,將 Cat.prototype 原有的 constructor 屬性覆蓋了。實(shí)例對(duì)象的 constructor 屬性向上查詢得到的是構(gòu)造函數(shù) Animal 。所以我們需要矯正一下 Cat.prototype 的 constructor 屬性,將其設(shè)置為構(gòu)造函數(shù) Cat 。
圖示
優(yōu)點(diǎn)
兼容性比較好,并且實(shí)現(xiàn)較為簡(jiǎn)單。
弊端
使用 new 操作符帶來的弊端是,執(zhí)行 new 操作符的時(shí)候,會(huì)執(zhí)行一次構(gòu)造函數(shù)將構(gòu)造函數(shù)中的屬性綁定到這個(gè)實(shí)例對(duì)象。這樣就多執(zhí)行了一次構(gòu)造函數(shù),將原本屬于 Animal 實(shí)例對(duì)象的屬性混到 prototype 中了。
第三版代碼
考慮到第二版的弊端,我們使用一個(gè)空構(gòu)造函數(shù)來作為中介函數(shù),這樣就不會(huì)將構(gòu)造函數(shù)中的屬性混到 prototype 中,并且減少了多執(zhí)行一次構(gòu)造函數(shù)帶來的性能損耗。
代碼如下:
function Animal(name){this.name = name }function Cat(name){Animal.call(this,name) } function Func(){} Func.prototype = Animal.prototypeCat.prototype = new Func() Cat.prototype.constructor = Cat 復(fù)制代碼圖示
ES6
使用 ES6 就方便多了。可以使用 extends 關(guān)鍵字實(shí)現(xiàn)繼承, 復(fù)用父原型中的代碼。使用 super 關(guān)鍵字來復(fù)用父構(gòu)造函數(shù)中的代碼。
代碼如下:
class Animal {constructor(name){this.name = name}eat(){console.log('eat')} } class Cat extends Animal{constructor(name){super(name)} }let cat = new Cat('Tom') console.log(cat.name) // Tom cat.eat() // eat 復(fù)制代碼相關(guān)知識(shí)點(diǎn)
- 理性分析 JavaScript 中的 this
- 理性分析 JavaScript 中的原型
參考書籍
- 《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》
- 《Java核心技術(shù) 卷Ⅰ(第9版)》
總結(jié)
以上是生活随笔為你收集整理的手把手教你如何实现继承的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2020计算机保研系列「最终篇」 ——
- 下一篇: React开发(174):ant des