關于JavaScript繼承和原型鏈

Before we get started

首先明確,JS的繼承是由原型鏈來實現的。(即使在ES6中class的extends,也更像是一種語法糖)

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

C++和Java使用new命令時,都會調用"類"的構造函數(constructor)
在Javascript語言中,new命令后面跟的不是類,而是構造函數
在 JavaScript 中,構造器其實就是一個普通的函數。當使用 new 操作符 來作用這個函數時,它就可以被稱為構造方法(構造函數)。

那么new運算符具體干了什么呢?

var obj  = {};
obj.__proto__ = F.prototype;
F.call(obj);

var o = new Foo();

// JavaScript 實際上執行的是:
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);

關于_proto_和prototype

那么上面的_proto_和prototype又是什么呢?

對象具有屬性_proto_,可稱為隱式原型,一個對象的隱式原型指向構造該對象的構造函數的原型。
eg.

let o1 = new Object()
// 則
o1.__proto__ ===  Object.prototype // true

而Function是特殊的對象,在Function中,除了和其他對象一樣有_proto_對象以外,還有原型屬性(prototype),這個屬性指向原型對象,而原型對象有個叫constructor的屬性,指回原構造函數。
eg.

let f1 = new Foo()
// 則
f1.__proto__ === Foo.prototype
Foo.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

Foo.prototype.constructor === Foo

Object的原型對象用Object.prototype._proto_ = null表示原型鏈的最頂端,如此變形成了javascript的原型鏈繼承,同時也解釋了為什么所有的javascript對象都具有Object的基本方法。

Object關于prototype的兩個方法

// 等同于 B.__proto__= A
Object.setPrototypeOf(B, A)
// 從子類上獲取父類
Object.getPrototypeOf(B)

JavaScript實現繼承的幾種方式

構造繼承

function dog (name) {
    this.name = name
}
// 共享的屬性放在prototype中,只要更改prototype,所有實例的master屬性都會被更改
dog.prototype = { master: 'Fog' }

// 生成兩個實例對象
let dog1 = new dog('111')
let dog2 = new dog('222')

console.log(dog1.master) // Fog
console.log(dog2.master) // Fog

// 更改master
dog.prototype.master = 'Misty'

console.log(dog1.master) // Misty
console.log(dog2.master) // Misty

由于所有的實例對象共享同一個prototype對象,那么從外界看起來,prototype對象就好像是實例對象的原型,而實例對象則好像"繼承"了prototype對象一樣。

原型繼承

即:把父類的私有+公有的屬性和方法,都作為子類公有的屬性。
核心思想:<code>Child.prototype = new Parent()</code>
實現的本質是重寫了原型對象 ,通過將子類的原型指向了父類的實例,所以子類的實例就可以通過_proto_訪問到 Child.prototype 也就是 Parent的實例

實例繼承

核心思想:

function Child() {
    let instance = new Parent()
    return instance
}

拷貝繼承

核心思想:循環遍歷父類實例,然后父類實例的私有方法全部拿過來添加給子類實例

Call繼承

核心思想:

function Child() {
    Parent.call(this); //構造函數中的this就是當前實例
}

注意:call是function的方法

中間件繼承

核心思想:<code>Child.prototype._proto_ = Parent.prototype</code>

Object.create()

ECMAScript 5 中引入了一個新方法:Object.create()??梢哉{用這個方法來創建一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數。

ES6的extends

首先說明,ES6的Class中,所有定義的function方法(注意:在定義類的方法時,前面不需要加上 function 這個保留字),都是定義在prototype屬性上的。
即:在類的實例上調用方法,其實就是調用原型上的方法。
此外,在類的內部定義的所有方法都是不可枚舉的。(與ES5不同,注意)

Class之間可以通過extends關鍵字實現繼承。
子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承了父類的this對象。如果不調用super方法,子類就得不到this對象。
這與ES5不同:ES5是先創建子類的this,再將父類的方法添加到this上(Parent.apply(this))
而ES6:先創建父類的this,然后再用子類的構造函數修改this

而super指向父類的原型對象,所以定義在父類實例上的方法或屬性,是無法通過super調用的

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

推薦閱讀更多精彩內容

  • JavaScript面向對象程序設計 本文會碰到的知識點:原型、原型鏈、函數對象、普通對象、繼承 讀完本文,可以學...
    moyi_gg閱讀 771評論 0 2
  • 本文主要講ES5、ES6中類的定義、封裝和類的繼承,以及一些注意事項,文中除了參考引用一些資料外,也加入了很多自己...
    會飛小超人閱讀 1,133評論 1 4
  • 第3章 基本概念 3.1 語法 3.2 關鍵字和保留字 3.3 變量 3.4 數據類型 5種簡單數據類型:Unde...
    RickCole閱讀 5,149評論 0 21
  • 我們在平和的環境中保持快樂的狀態,天天都開心,天天好心情,心態變了,思想變了,小孩自由了,我們輕松了。我們繼續讀書...
    天地悠悠88閱讀 147評論 0 1
  • 記憶里,這些片段好像與我沒有大關系。 一只呆頭呆腦的鴨子在天上飛; 穿著品牌西服的男士在街上狂奔; 傍晚橘紅色光芒...
    周谷雨閱讀 231評論 0 0