JavaScript面向對象之繼承

寫在前面:

本文記錄的幾種繼承方式,努力向基於class語法糖實現的面向對象靠攏html

class Person {
    constructor(friends) {
        this.friends = friends == null ? [] : friends;
    }
    sayFriends() {
        console.log(`${this.name}'s friends are ${this.friends}`);
    }
}

class Man extends Person {
    constructor(name, age, friends) {
        super(friends);
        this.name = name;
        this.age = age;
    }

    sayName() {
        console.log(this.name);
    }

    sayAge() {
        console.log(this.age);
    }
}

const xialuo = new Man('xialuo', 20, ['qiuya']);
const yuanhua = new Man('yuanhua', 21, []);

以上代碼能夠代表咱們的目的:函數

  1. 建立Person類,有屬性friends,方法sayFriends
  2. 建立Man類,在子類調用父類構造函數(super),繼承Person類,有屬性name、age,方法sayName,sayAge
  3. friends不能共享
  4. xialuo、yuanhua是Man也是Person的實例


1. 原型鏈繼承

將父類實例做爲子類的原型對象this

function Person(friends) {
    this.friends = friends == null ? [] : friends;
}
Person.prototype.sayFriends = function () {
    console.log(this.friends);
}


// 沒法向父類構造函數傳遞參數,第2點未達成
function Man(name, age, friends) {
    this.name = name;
    this.age = age;
}

// 原型鏈繼承
Man.prototype = new Person();

Man.prototype.sayName = function () {
    console.log(this.name)
}
Man.prototype.sayAge = function () {
    console.log(this.age);
}


var xialuo = new Man('xialuo', 20, ['qiuya']);
var yuanhua = new Man('yuanhua');

// friends被共享,第3點未達成
console.log(xialuo.friends); // []
console.log(yuanhua.friends); // []
xialuo.friends.push('teacherWang');
console.log(xialuo.friends); // ['teacherWang']
console.log(yuanhua.friends); // ['teacherWang']

// xialuo、yuanhua是Man也是Person的實例,第4點達成
console.log(xialuo instanceof Man); // true
console.log(xialuo instanceof Person); // true

缺點:spa

  1. 沒法向父類構造函數傳參
  2. 父類的引用類型friends被共享


2. 借用構造函數繼承

借用父類構造函數加強子類prototype

function Person(friends) {
    this.friends = friends == null ? [] : friends;
    this.personMethod = () => {
        console.log('personMethod run')
    }
}
Person.prototype.sayFriends = function () {
    console.log(this.friends);
}

function Man(name, age, friends) {
    Person.call(this, friends);
    this.name = name;
    this.age = age;
}
Man.prototype.sayName = function () {
    console.log(this.name)
}
Man.prototype.sayAge = function () {
    console.log(this.age);
}

var xialuo = new Man('xialuo', 20, ['qiuya']);
var yuanhua = new Man('yuanhua');

// 未繼承到父類Person的原型方法,第2點未達成
// xialuo.sayFriends(); // TypeError: xialuo.sayFriends is not a function
xialuo.personMethod(); // 'personMethod run'
console.log(xialuo.personMethod === yuanhua.personMethod); // false


// friends未被共享,第3點達成!
console.log(xialuo.friends); // ['qiuya']
console.log(yuanhua.friends); // []
xialuo.friends.push('teacherWang');
console.log(xialuo.friends); // [''qiuya, 'teacherWang']
console.log(yuanhua.friends); // []

// xialuo.__proto__和Person.prototype找不到同一個對象,第4點未達成
console.log(xialuo instanceof Man); // true
console.log(xialuo instanceof Person); // false

優勢:3d

  1. 解決原型鏈繼承中沒法向父類傳參的問題

缺點指針

  1. instanceof 沒法判斷實例是父類的實例
  2. 沒法繼承父類Person的原型方法,只能繼承父類的自有方法,且沒法複用(構造方法形成的)

Lint:爲何要區分原型方法和自有方法?code

function Man() {
    // 自有方法sayHello
    this.sayHello = () => {
        console.log('hello')
    }
}
Man.prototype.sayWorld = () => {
    console.log('world');
}

const xialuo = new Man();
const yuanhua = new Man();

/**
 * 私有方法在每次調用構造函數new時都會從新建立,沒法複用。
 *
 * 解決辦法是將方法添加到原型對象prototype,實例會從Man.prototype調用函數,實現函數複用
 */

console.log(xialuo.sayHello === yuanhua.sayHello); // false
console.log(xialuo.sayWorld === yuanhua.sayWorld); // true


3. 組合式繼承

原型鏈模式 + 借用構造函數模式,集兩者之長htm

function Person(friends) {
    this.friends = friends == null ? [] : friends;
}
Person.prototype.sayFriends = function () {
    console.log(this.friends);
}

// 子類屬性處理
function Man(name, age, friends) {
    // 子類繼承父類屬性
    Person.call(this, friends); // 借用構造函數
    // 子類生成自有屬性
    this.name = name;
    this.age = age;
}

// 子類方法處理
Man.prototype = new Person(); // 原型鏈模式,繼承父類屬性
Man.prototype.constructor = Man; // lint: 修復因原型鏈模式改變的子類構造函數(否則會變成Person)
// 子類自有方法
Man.prototype.sayName = function () {
    console.log(this.name)
}
Man.prototype.sayAge = function () {
    console.log(this.age);
}


var xialuo = new Man('xialuo', 20, ['qiuya']);
var yuanhua = new Man('yuanhua');

// friends未被共享,第3點達成!
console.log(xialuo.friends); // ['qiuya']
console.log(yuanhua.friends); // []
xialuo.friends.push('teacherWang');
console.log(xialuo.friends); // [''qiuya, 'teacherWang']
console.log(yuanhua.friends); // []

console.log(xialuo instanceof Man); // true
console.log(xialuo instanceof Person); // true
優勢:
集原型鏈模式和借用構造函數的優勢

缺點:
建立一個xialuo實例,卻調用兩次構造函數對象

目前,咱們還須要解決組合式繼承的缺點,以獲得js繼承的最佳實踐


4. 原型式繼承

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

function Person(name, age, friends) {
    this.name = name;
    this.age = age;
    this.friends = friends == null ? [] : friends;
}

var p = new Person('xialuo', 20, ['qiuya']);
var xialuo = object(p);

p = new Person('yuanhua', 21, []);
var yuanhua = object(p);

能夠看到,object函數接收一個對象obj,做爲臨時對象Fn的原型對象,最終返回Fn。

也就是說,xialuo,yuanhua的屬性,全在xialuo.__proto__,yuanhua.__proto__,而不是實例對象xialuo,yuanhua上。

這一般會致使原型指針混亂而形成this指向不明的問題。—— 維爾希寧

對了,ES5提供一個方法Object.create()替代了上述的object函數,內部實現是同樣的。

<br>

5. 寄生組合式繼承

寄生組合式繼承是對組合式繼承的改造,結合原型式繼承,目的是解決組合式繼承中兩次調用父類構造函數的問題。

function object(obj) {
    function Fn() { }
    Fn.prototype = obj;
    return new Fn();
}

function Person(friends) {
    this.friends = friends == null ? [] : friends;
}
Person.prototype.sayFriends = function () {
    console.log(this.friends);
}

function Man(name, age, friends) {
    Person.call(this, friends);
    this.name = name;
    this.age = age;
}
Man.prototype = object(Person.prototype); // 在此處,減小一次父類構造函數調用
Man.prototype.constructor = Man;
Man.prototype.sayName = function () {
    console.log(this.name)
}
Man.prototype.sayAge = function () {
    console.log(this.age);
}

var xialuo = new Man('xialuo', 20, ['qiuya']);
var yuanhua = new Man('yuanhua');

console.log(xialuo.friends); // ['qiuya']
console.log(yuanhua.friends); // []
xialuo.friends.push('teacherWang');
console.log(xialuo.friends); // [''qiuya, 'teacherWang']
console.log(yuanhua.friends); // []

console.log(xialuo instanceof Man); // true
console.log(xialuo instanceof Person); // true

收工!

本文的思想和代碼參考:

JS實現繼承的幾種方式 - 幻天芒 - 博客園

面向對象的 JavaScript:封裝、繼承與多態

相關文章
相關標籤/搜索