一.原型鏈繼承
原型鏈繼承的原理很簡單,直接讓子類的原型對象指向父類實例,當子類實例找不到對應的屬性和方法時,就會往它的原型對象,也就是父類實例上找,從而實現對父類的屬性和方法的繼承
function Prant() { this.name = 'hello' } Prant.prototype.getName = function() { return this.name; } function child() {} // 子類 //讓子類的原型對象指向父類實例, 這樣一來在Child實例中找不到的屬性和方法就會到原型對象(父類實例)上尋找 child.prototype = new Prant(); // 根據原型鏈的規則,順便綁定一下constructor, 這一步不影響繼承, 只是在用到constructor時會需要 child.prototype.constructor = child; const alias = new child(); alias.name //hello alias.getName(); //hello
缺點:
- 由於所有Child實例原型都指向同一個Parent實例, 因此對某個Child實例的父類引用類型變量修改會影響所有的Child實例
- 在創建子類實例時無法向父類構造傳參, 即沒有實現
super()
的功能
二.構造函數繼承
構造函數繼承,即在子類的構造函數中執行父類的構造函數,並為其綁定子類的this
,讓父類的構造函數把成員屬性和方法都掛到子類的this
上去,這樣既能避免實例之間共享一個原型實例,又能向父類構造方法傳參
function Parent(name) { this.name = [name] } Parent.prototype.getName = function() { return this.name } function Child() { // 子類 Parent.call(this, 'hello') } const child1 = new Child(); const child2 = new Child(); child1.name[0] = '構造函數繼承'; console.log(child1.name) // 構造函數繼承 console.log(child2.name) //hello child2.getName(); // 找不到,報錯
缺點:
- 繼承不到父類原型上的屬性和方法
三.組合式繼承
將以上兩者組合起來使用
function Parent(name) { this.name = [name] } Parent.prototype.getName = function() { return this.name } function Child() { // 構造函數繼承 Parent.call(this, 'zhangsan') } //原型鏈繼承 Child.prototype = new Parent() Child.prototype.constructor = Child //測試 const child1 = new Child() const child2 = new Child() child1.name[0] = 'hello' console.log(child1.name) // ['hello'] console.log(child2.name) // ['zhangsan'] child2.getName() // ['zhangsan']
缺點:
- 每次創建子類實例都執行了兩次構造函數(
Parent.call()
和new Parent()
),雖然這並不影響對父類的繼承,但子類創建實例時,原型中會存在兩份相同的屬性和方法,這並不優雅
四.寄生式組合繼承
解決構造函數被執行兩次的問題, 我們將指向父類實例
改為指向父類原型
, 減去一次構造函數的執行
function Parent(name) { this.name = [name] } Parent.prototype.getName = function() { return this.name } function Child() { // 構造函數繼承 Parent.call(this, 'zhangsan') } //原型鏈繼承 // Child.prototype = new Parent() Child.prototype = Object.create(Parent.prototype) //將`指向父類實例`改為`指向父類原型` Child.prototype.constructor = Child //測試 const child = new Child() const parent = new Parent() child.getName() // ['zhangsan'] parent.getName() // 報錯, 找不到getName()
目前最成熟的繼承方式,js高級體現。
回顧:
說到js繼承,最開始想到的應該是是
原型鏈繼承
,通過把子類實例的原型指向父類實例來繼承父類的屬性和方法,但原型鏈繼承的缺陷在於
對子類實例繼承的引用類型的修改會影響到所有的實例對象
以及
無法向父類的構造方法傳參
。
構造函數繼承
, 通過在子類構造函數中調用父類構造函數並傳入子類this來獲取父類的屬性和方法,但構造函數繼承也存在缺陷,構造函數繼承
不能繼承到父類原型鏈上的屬性和方法
。
后面有了組合式繼承,但也有了新的問題,每次都會執行兩次父類的構造方法,最終有了寄生式組合式繼承。
