做爲 JavaScript
中最重要的內容之一,繼承問題一直是咱們關注的重點。那麼你是否清晰地知道它的原理以及各類實現方式呢html
閱讀這篇文章,你將知道:前端
這裏默認你已經清楚的知道構造函數、實例和原型對象之間的關係,若是並非那麼清晰,那麼推薦你先閱讀這篇文章 -- JavaScript 中的原型與原型鏈git
若是文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過github
如下↓web
繼承(inheritance)是面向對象軟件技術當中的一個概念。若是一個類別B
繼承自
另外一個類別A
,就把這個B
稱爲A的子類
,而把A
稱爲B的父類別
也能夠稱A是B的超類
。繼承可使得子類具備父類別的各類屬性和方法,而不須要再次編寫相同的代碼 ... 更多)
經過這些概念和圖示咱們不難知道繼承能夠在咱們的開發中帶來的便捷,那麼在 JavaScript
中如何去實現繼承呢?segmentfault
利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法
function SuperType() { this.name = 'tt'; } SuperType.prototype.sayName = function() { return this.name } function SubType() { this.name = 'oo'; } SubType.prototype = new SuperType() var instance = new SubType() instance.sayName() // oo instance instanceof SubType // true instance instanceof SuperType // ture
以上的試驗中,咱們建立了兩個構造函數 SuperType
和 SubType
,而且讓 SubType
的原型指向 SuperType
,SubType
也就繼承了 SuperType
原型對象中的方法。因此在建立 instance
實例的時候,實例自己也就具備了 SuperType
中的方法,而且都處在它們的原型鏈中app
SubType.prototype.constructor == SubType // false SubType.prototype.constructor == SuperType // true
須要注意的是:這個時候 SubType.prototype.constructor
是指向 SuperType
的,至關於重寫了 SubType
的原型對象。函數
用一張圖表示:學習
SubType.prototype
至關於 SuperType
的實例存在的,因此 SubType.prototype.constructor
就指向 SuperType
優勢:this
缺點:
SubType
添加原型方法,就必須在 new SuperType
以後添加(會覆蓋)在子類構造函數的內部調用超類型構造函數,經過apply
和call
實現
function SuperType(name) { this.name = name; this.colors = ['red', 'orange', 'black']; } function SubType() { SuperType.call(this, 'tt'); } var instance = new SubType() var instance1 = new SubType() instance.colors // ['red', 'orange', 'black'] instance.name // tt instance.colors.push('green'); instance.colors // ['red', 'orange', 'black', 'green'] instance1.colors // ['red', 'orange', 'black']
優勢:
call
能夠指定不一樣的超類)缺點:
僞經典繼承(最經常使用的繼承模式):將原型鏈和借用構造函數的技術組合到一塊兒。使用原型鏈實現對原型屬性和方法的繼承,經過構造函數來實現對實例屬性的繼承
function SuperType(name) { this.name = name; this.colors = ['red', 'orange', 'black']; } SuperType.prototype.sayName = function() { return this.name } function SubType() { SuperType.call(this, 'tt'); this.name = 'oo'; } // 這裏的 SubType.prototype.constructor 仍是指向 SuperType SubType.prototype = new SuperType(); var instance = new SubType(); var instance1 = new SubType(); instance.name // oo instance.sayName() // oo instance.colors.push('green'); instance.colors // ['red', 'orange', 'black', 'green'] instance1.colors // ['red', 'orange', 'black']
優勢:
缺點:
藉助原型鏈能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型
function obj(o) { function F(){} F.prototype = o; return new F(); } var person = { name: 'tt', age: 18, colors: ['red', 'green'] } var instance = obj(person); var instance1 = obj(person); instance.colors.push('black'); instance.name // tt instance.colors // ['red', 'green', 'black'] instance1.colors // ['red', 'green', 'black']
建立一個臨時的構造函數,而後將傳入的對象當作這個構造函數的原型對象,最後返回這個臨時構造函數的新實例。實際上,就是對傳入的對象進行了一次淺複製
ES5
經過新增 Object.create()
規範化了原型式繼承
更多 Object.create()語法請點擊 這裏
優勢:
缺點: 和原型鏈繼承基本一致,效率較低,內存佔用高(由於要拷貝父類的屬性)
建立一個僅用於封裝繼承過程的函數,在函數內部對這個對象進行改變,最後返回這個對象
function createAnother(obj) { var clone = Object(obj); clone.sayHi = function() { alert('Hi'); } return clone } var person = { name: 'tt', age: 18, friends: ['oo', 'aa', 'cc'], sayName() { return this.name } } var instance = createAnother(person) var instance1 = createAnother(person) instance.friends.push('yy') instance.name // 'tt' instance.sayHi() // Hi instance.friends // ["oo", "aa", "cc", "yy"] instance1.friends // ["oo", "aa", "cc", "yy"]
優勢:
缺點:
借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。經過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
function inherit(subType, superType) { var obj = Object(superType.prototype); // 建立對象 obj.constructor = subType; // 指定constructor subType.prototype = obj; // 指定對象 } function SuperType(name) { this.name = name; this.colors = ['red', 'orange', 'black']; } SuperType.prototype.sayName = function() { return this.name } function SubType() { SuperType.call(this, 'tt'); this.name = 'oo'; } inherit(SubType, SuperType) var instance = new SubType() instance.name // oo instance.sayName // oo instance instanceof SubType // true instance instanceof SuperType // true SubType.prototype.constructor == SubType // true
堪稱完美,只是實現稍微複雜一點
做爲 JavaScript
最重要的概念之一,對於繼承實現的方式方法以及它們之間的差別咱們仍是頗有必要了解的。
在實現繼承的時候,拷貝
也是一種頗有效的方式,因爲 JavaScript
簡單數據類型與引用類型的存在,衍生出了 淺拷貝
與 深拷貝
的概念,那麼它們又是什麼,怎麼去實現呢
且聽下回分解,哈哈
週末愉快
最後,推薦一波前端學習歷程,不按期分享一些前端問題和有意思的東西歡迎 star 關注 傳送門
JavaScript 高級程序設計