首頁 > 軟體

JavaScript中常見的繼承方式總結

2022-11-15 14:00:06

JS和Java中雖然都有物件的概念,但這兩種物件卻大有不同。Java的物件是基於類建立的,JS的物件卻是基於一個特殊的物件——原型物件——建立的,之前看到一個蓋房子的比喻,在Java中蓋房子是先畫好圖紙再蓋房子,JS中蓋房子卻是先蓋一個樣板房再蓋其他房子,覺得也挺貼切。

所以JS中的繼承和Java中的繼承就大有不同了,是基於原型物件的,如果兩個物件形成繼承關係,那必然是其中一個物件的原型鏈上存在一個指標指向另一個物件。即使JS中的兩個類宣告了繼承關係,也是表現在原型物件上。比如:

class A {
    say() {
        console.log('say: hello!');
    }
}

class B extends A {
    constructor() {
        super();
    }
}

console.log(A.prototype); // {constructor: ƒ, say: ƒ}
console.log(B.__proto__); // class A {}
console.log(B.prototype); // A {constructor: ƒ}

首先,類是JS中函數的語法糖,並且在JS中函數本身也是物件,也就是說A和B是兩個物件,所以extends操作使得B自身的原型屬性__proto__指向了A,相當於const B = Object.create(A);

其次,類的繼承關係也影響其生成的範例,眾所周知,函數本身存在一個特殊的物件屬性:prototype,函數經過構造呼叫產生的範例的原型屬性__proto__是指向這個物件的,而extends操作修改了B的prototype物件,所以B範例上的原型屬性__proto__也就被修改了,通過B範例的原型屬性__proto__能找到A的prototype,即在B範例的原型鏈上能找到A的prototype。

const b = new B();
console.log(b.__proto__); // A {constructor: ƒ} 即B.prototype
console.log(b.__proto__.__proto__); // {constructor: ƒ, say: ƒ} 即A.prototype

在JS中使用字面量定義的物件時,其預設的原型屬性__proto__指向Object的prototype物件,相當於預設繼承自Object,所以字面量物件可以呼叫Object的實體方法。

可以使用isPrototypeOf來判斷一個物件是否在另一個物件的原型鏈上。

由上述可知,JS中的繼承關係與原型物件密切相關,為了達到繼承的關聯關係(共用某些屬性和方法),就要從原型物件著手:

1.使用Object.create的方式建立物件,使兩個物件直接產生繼承關係

const o1 = {
    name: 'o1',
    age: 18,
    walk() {
        console.log('walking...')
    }
};
const o2 = Object.create(o1);
console.log(o2.__proto__); // {name: 'o1', age: 18}
console.log(o2.walk()); // walking...
console.log(o1.isPrototypeOf(o2)); // true

2.使用new操作建立物件,使產生的範例和類(或函數)的原型物件產生繼承關係

const b = new B();
console.log(B.prototype); // A {constructor: ƒ}
console.log(b.__proto__); // A {constructor: ƒ} 即B.prototype
console.log(B.prototype.isPrototypeOf(b)); // true

3.使用extends關鍵字使類形成繼承關係,擴充套件類範例的原型鏈

class A {
    say() {
        console.log('say: hello!');
    }
}

class B extends A {
    constructor() {
        super();
    }
}

console.log(A.prototype); // {constructor: ƒ, say: ƒ}
const b = new B();
console.log(b.__proto__.__proto__); // {constructor: ƒ, say: ƒ} 即A.prototype
console.log(A.isPrototypeOf(B)); // true
console.log(A.isPrototypeOf(b)); // false
console.log(A.prototype.isPrototypeOf(b)); // true

4.修改函數的prototype屬性使函數形成繼承關係,擴充套件函數範例的原型鏈

function C() {
    this.name = 'c';
    this.operation = function() { return 'printing...'};
}
function D() {}
D.prototype = new C();
const d = new D();
console.log(d.__proto__.__proto__ === C.prototype); // true
console.log(C.prototype.isPrototypeOf(d)); // true
console.log(D.prototype.isPrototypeOf(d)); // true

這裡存在一個問題,就是子類範例化時無法向父類別的建構函式傳參

5.盜用父類別建構函式

在函數內部通過call或apply呼叫父類別函數(非構造呼叫),可繼承父類別範例自身(非原型物件)的屬性和方法(相當於把子類範例(即this)傳遞進父類別函數,對這個this做了一遍操作),雖然可在初始化時傳遞引數給父類別,但無法形成原型鏈

function E() {
    C.call(this);
    this.do = function () { return 'do homework'; }
}
const e = new E();
console.log(E.prototype.isPrototypeOf(e)); // true
console.log(C.prototype.isPrototypeOf(e)); // false
console.log(e); // E {name: 'c', operation: ƒ, do: ƒ}
console.log(e.do()); // do homework

子類產生的範例無法對父類別及其原型物件應用instanceof和isPrototypeOf方法。

此時如果父類別想共用方法給子類,必須把方法直接在定義在函數內部,繫結到範例上,而無法通過父類別的prototype物件共用。

6.結合4和5,使得子類範例可繼承父類別原型物件的屬性和方法,且能形成原型鏈

function E() {
    C.call(this);
    this.do = function () { return 'do homework'; }
}
E.prototype = new C();
const e = new E();
console.log(E.prototype.isPrototypeOf(e)); // true
console.log(C.prototype.isPrototypeOf(e)); // true
console.log(e); // E {name: 'c', operation: ƒ, do: ƒ}
console.log(e.do()); // do homework

7.用Object.create()替換new父類別範例來重寫子類的原型物件

function inheritatePrototype(subT, superT) {
  let proto = Object.create(superT.prototype);
  proto.constructor = subT;
  subT.prototype = proto;
}

inheritatePrototype(E, C);

可以捨去new中不需要的操作

8.通過工廠方式共用屬性和方式

類似工廠函數,但不是用裸的Object,以某種方式取得物件(如new等返回新物件的函數),對此物件加屬性或方法以增強功能,並返回物件。

function createAnother(original) {
  let clone = Object.create(original);
  clone.xx = xxx;
  return clone;
}

適合主要關注物件,而不在乎型別和建構函式的場景

存在的問題: 必須在建構函式中定義方法(屬於範例非原型物件的方法),函數不能重用

到此這篇關於JavaScript中常見的繼承方式總結的文章就介紹到這了,更多相關JavaScript繼承方式內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com