原文地址: JavaScript — Inheritance, delegation patterns and Object linking
作者: NC Patro
學習 JavaScript (原型繼承) 中的繼承,行為/物件委託模式和關聯到其它物件的物件
什麼是繼承
在大多數基於類的面嚮物件語言中,繼承是一種讓一個物件可以獲得另一個物件所有的屬性和方法的機制。雖然在 ES2015
中 提出了 class
關鍵字,但 JavaScript
並不是一門基於類的語言,它僅僅只是語法糖,本質上還是原型鏈的方式。
經典繼承與原型繼承
經典繼承(非 JavaScript)
Vehicle
是父類,v1
v2
是Vehicle
的例項。Car
是Vehicle
的子類而c1
和c2
是Car
的例項。- 當我們繼承類時,經典繼承建立了一個來自父類行為的拷貝到子類中,然後父子類就是獨立的實體了。
- 這就像是汽車是用工具以及汽車圖紙造出來的,但造完以後兩者都是獨立的個體,因為它就是一份拷貝所以它們之間沒有關聯,這就是所有箭頭向下(屬性和行為向下傳遞)的原因。
原型繼承(行為委託模式)
v1
和v2
關聯到Vehicle.prototype
因為它們是通過 new 建立的。- 同樣的,
c1
和c2
關聯到Car.prototype
而Car.prototype
關聯到Vehicle.prototype
。 - 在
JavaScript
中當我們建立一個物件時,它不是複製屬性或者行為,而是建立一個連結. 在繼承一個類時也會建立類似的連結。 - 與經典的非
JavaScript
繼承相比,所有連結向著相反的方向,因為它是行為委託連結。 這些連結稱為原型鏈 - 這個模式稱為行為委託模式,通常稱為
JavaScript
中的 原型繼承。
你可以通過這篇文章 JavaScript-原型 去深入理解 原型鏈
原型繼承的例子
- 使用
Object.create()
實現經典繼承。 - 在下列程式碼片段中,
Car.prototype
和Vehicle.prototype
在Object.create()
函式的幫助下相連線。
// Vehicle - 超類
function Vehicle (name) {
this.name = name;
}
// 超類的方法
Vehicle.prototype.start = function () {
return "engine of " + this.name + " starting...";
}
// Car - 子類
function Car (name) {
Vehicle.call(this, name); // 呼叫超類的建構函式
}
// 子類擴充套件超類
Car.prototype = Object.create(Vehicle.prototype);
// 子類的方法
Car.prototype.run = function () {
console.log("Hello " + this.start());
}
// 子類的例項
var c1 = new Car("Fiesta");
var c2 = new Car("Baleno");
// 訪問 內部訪問了超類方法 的子類方法
c1.run(); // "Hello engine of Fiesta starting..."
c2.run(); // "Hello engine of Baleno starting..."
複製程式碼
- 在上述程式碼中,由於下面的原型鏈,物件
c1
可以訪問到run()
方法和start()
方法. 如下圖所示,我們可以看到c1
沒有這樣的方法,但它有向上的連結。 - 上面程式碼中的
this
只不過是每個方法當前的執行上下文,即c1
和c2
。
你可以瀏覽這篇文章 JavaScript-關於 this 和 new 的所有內容 來詳細瞭解 this 關鍵字
上述程式碼的圖解表示:
與其它物件關聯的物件
- 現在我們將會簡化先前的繼承示例程式碼,只關注物件與物件之間的連結。
- 所以我們將會嘗試移除 .prototype , constructor 和 new 關鍵字,只考慮物件。
- 我們將會使用
Object.create()
函式來建立函式之間的所有連結。
下面是先前示例程式碼的簡化版:
// 包含初始化方法的基礎物件
var Vehicle = {
init: function (name) {
this.name = name;
},
start: function () {
return "engine of " + this.name + "starting...";
}
}
// 在子物件和基礎物件之間建立的委託連結
var Car = Object.create(Vehicle);
// 子物件的方法
Car.run = function () {
console.log("Hello " + this.start());
};
// 具有委託連結的例項物件指向子物件
var c1 = Object.create(Car);
c1.init('Fiesta');
var c2 = Object.create(Car);
c2.init('Baleno');
c1.run(); // "Hello engine of Fiesta starting..."
c2.run(); // "Hello engine of Baleno starting..."
複製程式碼
上述程式碼的圖解展示
- 現在我們可以看到,我們如何消除了 new ,所有 .prototype ,建構函式和呼叫方法的複雜性,並且仍然實現了相同的結果。
- 唯一重要的是
c1
連結到一個物件然後再連結到另一個物件,依次類推。 - 這也被稱作物件委託模式。
總結
為了規避複雜性,在程式碼中使用原型繼承與原型鏈前先理解它們是很重要的。
參考: You Don't Know JS 系列叢書