淺談JS繼承

什么是繼承

根據維基百科解釋,可以簡單概括為:
繼承是類與類之間的關系,其作用是使得子類具有父類別的各種屬性和方法。

JS 里的原型繼承模型

JS:不好意思,我沒有類。(即使是ES6中的類也是語法糖)
JavaScript 是基于原型實現面向對象的,那么在JS中,面向對象概念中的繼承自然也是基于原型。

當談到繼承時,JavaScript 只有一種結構:對象。每個實例對象(object )都有一個私有屬性(稱之為[[prototype]])指向它的原型對象(prototype)。該原型對象也有一個自己的原型對象 ,層層向上直到一個對象的原型對象為 null。根據定義,null 沒有原型,并作為這個原型鏈中的最后一個環節。

幾乎所有 JavaScript 中的對象都是位于原型鏈頂端的 Object 的實例。

有關原型之前寫過博客JavaScript原型和原型鏈,對理解下面內容有幫助。
雖然沒有傳統語言意義上的類,但是 JS語言 使用構造函數生成對象,實現面向對象程序設計。

說了這么多,JS 中的繼承到底是什么?

可以簡單理解為:兩次的原型搜索就是繼承。
數組 a 從 Array 中原型搜索到 toString 屬性,只是實例屬性;a 從 Array 中原型搜索到 (Array 從 Object 中原型搜索到的)valueOf 屬性,可以稱為繼承。

接下來我們用代碼實現一下繼承

1. 使用 prototype 實現繼承

prototype 的作用:為構造函數內添加實例對象之間的共有屬性

明確 JS 內的繼承
以下面代碼為例

// 構造一個 人類
function Human(name){
  this.name = name
}
// 給所有 人類 添加一個 跑 的共有屬性
Human.prototype.run = function(){
  console.log("我叫"+this.name+",我在跑")
  return undefined
}
// 構造一個 男人類
function Man(name){
  Human.call(this, name)
  this.gender = '男'
}
// 所有 男人 都有好戰屬性
Man.prototype.fight = function(){
  console.log('糊你熊臉')
}

可以看到名為 ada 的人只有 name、gender 和fight 這些 Man 構造函數里面含有的屬性,而沒有我們希望的 Human 應該有的 run 的屬性。
目標:假如我們有方法讓 ada 有了 Man 里面沒有的 run 屬性,即我們自己實現了 Man 繼承 Human 的過程。
根據我原型知識的博客里面的內容我們知道,我們可以直接:

Man.prototype.__proto__ = Human.prototype

可以看到 Man 指向了 Human 而不是直接指向 Object,ada2 繼承了來自 Human 的 run 屬性

但是在實際編程過程中直接操作 __ proto __ 這個非標準但許多瀏覽器(IE不支持)實現的屬性是不規范的。

那怎么辦?
new 可不可以?

Man.prototype = new Human()

直接用上述代碼不行,因為在 new 的過程中,雖然new 內部實現了 Man.prototype.__proto__ = Human.prototype 這一個過程,但是由于 new 同時會在內部執行構造函數,而在執行過程中我們未傳 name,因此上圖中 Human 的 name 屬性顯示 undefined

那么我們只要避免這個過程中 Human 執行就可以了

var a = function(){}
a.prototype = Human.prototype
Man.prototype = new a() 

通過上面三行代碼,即實現了沒有內部執行空函數的 new

2. ES6 實現繼承

上面代碼的 ES6 版本

// ES6 寫法
class Human{
     constructor(name){
         this.name = name
     }
     run(){
         console.log("我叫"+this.name+",我在跑")
         return undefined
     }
 }
 class Man extends Human{ // extends 實現上述繼承過程
     constructor(name){
         super(name) // 調用構造函數:'超類'
         this.gender = '男'
     }
     fight(){
         console.log('糊你熊臉')
     }
 }

文章開頭已經提到, ES6 的 class 是語法糖,其實質就是函數,而上述用 class 實現繼承的過程,還是基于原型鏈(和 ES5 的是不是完全一致)

總結:

JS 繼承的原型寫法相對 ES6 的寫法看上去似乎更復雜,但是事實上更好理解;class 的寫法更符合面向對象編程的思維,由于是語法糖因而自然寫法簡便,但其有一定局限性。

原型繼承模型本身實際上比經典模型更強大


感謝閱讀
本文僅供個人學習使用

部分參考:繼承與原型鏈

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容