JavaScipt中三種常用的繼承方法

JS中的繼承:一個對象可以使用另一個對象的成員(屬性和方法)

目的:方便代碼的複用

繼承的三種主要方式

  1. 原型鏈繼承
  2. 借用構造函數繼承
  3. 組合繼承

原型鏈繼承

原型鏈繼承的方法有兩種:
1.往原型對象身上逐一添加屬性和方法

//創建一個構造函數Person,並且身上並沒有任何屬性和方法
function Person() {}

//給構造函數的原型上添加了一些屬性和方法
Person.prototype.color = "lime";
Person.prototype.sayHi = function () {
    console.log("hello");
};
Person.prototype.work = function () {
    console.log("work");
};
Person.prototype.running = function () {
    console.log("running");
};
Person.prototype.eat = function () {
    console.log("eat");
};

//通過new創建實例對象p
var p = new Person();
//此時實例對象p調用sayHi方法
p.sayHi();
//輸出值爲hello,p繼承了原型對象身上的方法

在上述代碼中我們可以看到實例對象p本身並沒有任何屬性方法,卻可以調用sayHi方法,這就是所謂的原型鏈繼承。

優勢:少量添加成員比較方便
弊端:添加的成員太多會導致代碼重複問題

2.原型替換
把原型替換成新的對象,給新的對象添加成員。

//創建一個構造函數Person,並且身上並沒有任何屬性和方法
function Person() {}

//此時把構造函數的原型替換成了一個新的對象,並且在對象上添加了屬性和方法
Person.prototype = {
//constructor屬性丟失,需要重新添加這一屬性,讓原型對象指向構造函數
    constructor: Person,
    color: "lime",
    sayHi: function () {
        console.log("hello");
    },
    work: function () {
        console.log("work");
    },
    // ...
}


var p = new Person();
    // 實例對象p繼承自 Person的原型。
    // console.log(p);
p.sayHi();
//輸出值爲hello,實例對象p繼承了新的原型對象上的屬性和方法

優勢:大量添加成員的時候,可以節約代碼
弊端:替換新的原型對象會導致constructor屬性丟失,需要在新的對象中添加這一屬性,讓新原型對象重新指向構造函數

借用構造函數繼承

//創建一個Person構造函數,函數身上有name,age,gender三個屬性
function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//創建一個Chinese構造函數,函數身上有skin這一屬性
function Chinese(name, age, gender, skin){
    this.skin = skin;
}

var xm = new Chinese("xm", 20, "male", "黃");
console.log(xm);
//輸出結果Chinese {skin: '黃'}
//此時想讓實例對象xm能夠擁有name,age,gender三個屬性

此時可以使用上下文調用模式(借用方法模式)中的call和apply方法
戳這裏詳細瞭解call,apply,bind

function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

function Chinese(name, age, gender, skin){
    //  讓xm借用了構造函數Person,從而繼承到了Person添加的屬性。
    // call實現借用
    // 1. call把Person函數給調用了
    // 2. call的第一個參數this指向了 xm
    // 3. call的第一個參數可以用來修改函數內的this指向,Person函數內的this指向了xm
    // 4. call的其他參數用來給函數傳遞實參
    Person.call(this, name, age, gender);
    
    // apply實現借用
    //Person.apply(this, [name, age, gender]);
    
    this.skin = skin;
}
var xm = new Chinese("xm", 20, "male", "黃");
console.log(xm);
//輸出結果:Chinese {name: "xm", age: 20, gender: "male", skin: "黃"}

組合繼承

原型鏈繼承+借用構造函數繼承

//創建一個Person構造函數,函數身上有name,age,gender三個屬性
function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//此時給Person的原型添加了一個sayHi方法
Person.prototype.sayHi = function () {
    console.log("hello, 我是 " + this.name);
}

//創建一個Chinese構造函數,函數身上有skin這一屬性
function Chinese(name, age, gender, skin){
    this.skin = skin;
}

var xm = new Chinese("xm", 20, "male", "黃");
xm.sayHi();
//輸出結果:報錯

上述代碼中我們可以看到,實例對象xm身上並沒有這個方法,原型鏈上也沒有這個方法,所以會導致代碼報錯。

此時我們可以使用原型替換代碼如下:

//創建一個Person構造函數,函數身上有name,age,gender三個屬性
function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//此時給Person的原型添加了一個sayHi方法
Person.prototype.sayHi = function () {
    console.log("hello, 我是 " + this.name);
}

//創建一個Chinese構造函數,函數身上有skin這一屬性
function Chinese(name, age, gender, skin){
	// 借用構造函數繼承
    //  讓xm借用了構造函數Person,從而繼承到了Person添加的屬性。
    Person.call(this, name, age, gender);
    this.skin = skin;
}

//xm原來的原型鏈:xm ==> Chinese.prototype ==> Object.prototype ==> null;
//替換後xm的原型鏈:xm ==> Person.prototype ==> Object.prototype ==> null;
Chinese.prototype = Person.prototype;

var xm = new Chinese("xm", 20, "male", "黃");
xm.sayHi();
//輸出結果:hello, 我是 xm

這種方法雖然成功讓xm調用了sayHi的方法,但是有弊端。
弊端:此時給Chinese的原型對象添加一個sayHi方法,但是Chinese的原型對象變成了Person.prototype,所有會把Person.prototype的方法覆蓋。

優化代碼如下:

//創建一個Person構造函數,函數身上有name,age,gender三個屬性
function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//此時給Person的原型添加了一個sayHi方法
Person.prototype.sayHi = function () {
    console.log("hello, 我是 " + this.name);
}

//創建一個Chinese構造函數,函數身上有skin這一屬性
function Chinese(name, age, gender, skin){
	// 借用構造函數繼承
    //  讓xm借用了構造函數Person,從而繼承到了Person添加的屬性。
    Person.call(this, name, age, gender);
    this.skin = skin;
}

//xm原來的原型鏈:xm ==> Chinese.prototype ==> Object.prototype ==> null;
//替換後xm的原型鏈:xm ==> Person的實例對象 ==> Person.prototype ==> Object.prototype ==> null;
Chinese.prototype = new Person();
//優化後的目的:
//1. xm還可以使用sayHi方法
//2. 操作Chinese.prototype,不會影響Person.prototype

//同樣替換新的原型對象會導致constructor屬性丟失,手動添加constructor屬性
Chinese.prototype.constructor = Chinese;

var xm = new Chinese("xm", 20, "male", "黃");
xm.sayHi();
//輸出結果:hello, 我是 xm

最後說下組合繼承的優勢
優勢:借用構造函數繼承主要爲了繼承屬性,原型鏈繼承主要繼承方法,組合繼承結合了兩者的優勢,既能繼承屬性也能繼承方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章