JavaScript 中什麼樣的繼承纔是好的繼承?

JavaScript 繼承

前言

理解對象和繼承是我們學習設計模式,甚至是閱讀各種框架源碼的第一步。上一篇文章,筆者已經把JavaScript對象進行了梳理,今天我們來一起學習繼承。

繼承

原型鏈

基本思想:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法,也就是將一個原型對象指向另一個引用類型的實例。
Example:

function Parent() {
  this.property = true
}
Parent.prototype.getParentValue = () => this.property

function Child() {
  this.childProperty = false
}

Child.prototype = new Parent()
Child.prototype.getChildValue = () => this.childProperty

let instance = new Child()
console.log(instance.getParentValue()) // true

Child 通過創建 Parent 實例,並將實例賦值給 Child.prototype 的方式,繼承了 Parent 。本質是利用實例重寫了原型對象。這樣,Child.prototype 擁有了 Parent 的全部屬性和方法,同時其內部指針也指向了 Parent.prototype
通過實現原型鏈,本質上拓展了原型搜索機制。

添加方法(謹慎):
字類型有時需要重寫超類型中的某個方法,或新增超類型中不存在的方法,一定要將給原型添加方法的代碼放在被替換原型的語句後面。
Example:

function Parent() { 
   this.property = true; 
}

Parent.prototype.getParentValue = () => this.property

function Child() { 
   this.childProperty = false; 
}

//繼承了 SuperType 
Child.prototype = new Parent();

//添加新方法 
Child.prototype.getChildValue = () => this.childProperty

//重寫超類型中的方法 
Child.prototype.getParentValue = () => false

let instance = new ChildType(); 
console.log(instance.getParentValue());   //false

原型鏈繼承存在的問題:

  1. 創建子類型實例時,不能向父類的構造函數中傳遞參數
  2. 父子構造函數的原型對象之間存在共享問題
    example:
function Parent(){ 
    this.colors = ["red", "blue", "green"];
}
function Child() {}
Child.prototype = new Parent();

let instanceChild = new Child();
instance1.colors.push("black");
console.log(instanceChild.colors);   //"red", "blue", "green","black"
//當我們改變colors的時候, 父構造函數的原型對象的也會變化
let instanceParent = new Parent();
console.log(instanceParent.colors);   //"red", "blue", "green","black"

構造函數

基本思想:在子類構造函數的內部,調用父類的構造函數
Example:

function Parent(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
function Child() {
    Parent.call(this, "jerry");  // 繼承了 Parent
    this.age = 22;
}

let instanceChild = new Child();
instanceChild.colors.push("black");
console.log(instanceChild.name);    // jerry
console.log(instanceChild.colors);   //"red", "blue", "green","black"

let instanceParent = new Parent();
console.log(instanceParent.colors);   //"red", "blue", "green"

構造函數問題:

  1. 方法都在構造函數中定義,因此函數很難複用
  2. 在父類原型對象中定義的方法,子類無法繼承

組合繼承

簡單的說:原型鏈+構造函數
基本思想:原型鏈實現對原型屬性和方法的繼承,構造函數實現對實例屬性的繼承
Example:

function Parent(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayName = () => { console.log(this.name) }

function Child(name, age) {
    Parent.call(this, name);    // 繼承屬性
    this.age = age;
}

// 繼承方法
Child.prototype = new Parent();
Child.prototype.constructor = Child;
Child.prototype.sayAge = () => { console.log(this.age) }

let instanceChild = new Child("jerry", 23);
instanceChild.colors.push("black"); 
console.log(instanceChild.colors);  //"red", "blue", "green","black"
instanceChild.sayName();        // jerry
instanceChild.sayAge();         // 23

存在問題:

  1. 若再添加一個子類型,給其原型單獨添加一個方法,那麼其他子類型也同時擁有了這個方法,因爲它們都指向同一個父類型的原型
  2. 無論在什麼情況下都會調用兩次父類的構造函數,我們不得不在調用子類構造函數時,重寫這些屬性。

原型式繼承

基本思想: 本質是對繼承對象執行了一次淺拷貝。
example:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
// ES5內置了object.create()方法可以以上方法的功能。
let parent = {
    name: "jerry",
    friends: ["marry", "sandy"]
}

let Child1 = object(parent);
Child1.name = "barbie";
Child1.friends.push("Rob");

let Child2 = object(parent);
Child2.name = "Cos";
Child2.friends.push("Linda");

console.log(parent.friends);    //"marry", "sandy","Rob","Linda"

存在問題:

  1. 與原型鏈繼承一樣,父子對象之間存在共享問題
  2. 無法實現複用

寄生式繼承

基本思路:在原型式繼承外面套了一層函數,在該函數內部增強對象。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

// 殼子
function createAnother(original) {
    let clone = object(original);
    clone.sayHi = () => { console.log("Hi") }
    return clone;
}

存在問題:

  1. 複用率賊低

寄生組合式繼承

基本思想: 利用構造函數來繼承屬性,利用原型鏈的混成形式來繼承方法。
example:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function inheritPrototype(child, parent) {
    let prototype = object(parent.prototype)
    prototype.constructor = child;
    child.prototype = prototype;
}

function Parent(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayName = () => { console.log(this.name) }

function Child(name, age) {
    Parent.call(this, name);    // 繼承屬性
    this.age = age;
}
inheritPrototype(Child, Parent)

Child.prototype.sayAge = () => this.age

目前來說,這是最好的繼承方式。

結束

懶癌發作,明明早就寫好了,一直把他丟棄在電腦硬盤裏。幾周之後,我掙扎着打開MWeb,稍做修葺,趕個結束語。算是畫上一個句號吧。最後,不得不說,高級3,真是一本不可多得的好書,把對象、繼承講得如此清晰明瞭。

參考

  • 《JavaScript高級程序設計》第3版
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章