JS中的多種繼承方式

金色小芝麻發表於2020-04-04

JS本身是基於物件導向開發的程式語言:所以我們學習JS主要就是學習類,以及類的例項,在學習類的原型上提供的方法;

類:

  • 封裝、繼承、多型

一、類的封裝

  • 類也是一個函式,把實現一個功能的程式碼進行封裝,以此實現“低耦合高內聚”

二、多型:過載、重寫

  • 重寫:子類重寫父類上的方法(伴隨著繼承執行的)
  • 過載:相同的方法,由於引數或者返回值不同,具備了不同的功能(JS中不具備嚴格意義上的過載,JS中的過載:同一個方法內,根據傳參不同實現不同的功能)

1、在後臺語言當中:

  • 過載定義:
    • 指的是在方法名字相同,都叫fn,但是引數的型別或者引數的個數不同,所以導致這兩個方法不是一個方法,而變成了兩個方法;(我們認為這就是方法的過載)
  • 過載的目的:(為了分擔壓力的)
    • 正常情況下,客戶端向伺服器發請求,一臺伺服器允許多個客戶端同時發請求,所以伺服器要有很強的抗壓能力,
    • 如果說我們把所有的功能(比如傳兩個引數幹什麼,傳一個引數幹什麼)都放在一個方法中,那這個方法承載的壓力就會比較大,
    • 所以我們一般通過過載的方式來分發出這個方法承載的壓力;
//=> 在後臺語言中,
public void fn(int x,init y){

}
public void fn(int x){

}
fn(10,20);  執行第一個FN
fn(10);  執行第二個FN
fn('小芝麻');  報錯  

複製程式碼

2、在JS語言中:這種方式並不是過載

  • 由於JS中的變數提升機制,所以只執行第二個
  • 所以說JS中不具備嚴格意義上的過載,
function fn(x, y) {

}

function fn(x) {

}
fn(10, 20);  執行第二個FN
fn(10); 執行第二個FN 
複製程式碼
  • JS中的過載:同一個方法內,根據傳參不同實現不同的功能
function fn(x, y) {
    if(y===undefined){
        // ...
        return;
    }
    // ....
}
fn(10);
fn(10, 20); 
複製程式碼

三、繼承:子類繼承父類中的方法

這裡想到了一個笑話:

A去醫院檢查,被查出是“類風溼性關節炎”
A:好鬱悶,得了…
B:這個病注意點就好了啊
A:不行,因為我們打算要孩子
B:你這個病和孩子有啥關係啊
….
10min  A的腦子中 "類風溼性關節炎” => 類是繼承的
複製程式碼

在生物學上的繼承類似於:

父親:基因
   AAA  BBB  CCC

兒子:繼承了父親的基因(把父親的部分基因,直接拷貝到自身上)
   AAA  BBB 
   兒子基因突變  AAB  BBA,但是對父親是沒有影響的
複製程式碼

正常的後臺語言中的繼承,就是這種拷貝式的繼承,但是我們JS中的繼承並不是這種繼承;

1、繼承的定義:

  • 在JS語言中,它的繼承和其它程式語言還是不太一樣的

2、繼承的目的:

  • 讓子類的例項同時也具備父類中私有的屬性和公共的方法

3、JS中繼承方案

第一種:原型繼承(讓子類的原型等於父類的例項即可)

function Parent() {
	this.x = 100;
}
Parent.prototype.getX = function getX() {
	return this.x;
};

function Child() {
	this.y = 200;
}
//=> 讓子類的原型等於父類的例項
Child.prototype = new Parent; //=>原型繼承

Child.prototype.getY = function getY() {
	return this.y;
};

let c1 = new Child;
console.log(c1); 
複製程式碼

現在c1 能用的方法是:私有的 y 和公有的 getY方法,以及Object上的公有方法;

1.原理

  • 子類的例項,能夠用子類私有的和原型上公有的
  • 父類的例項,可以使用 父類私有和原型上公有的

所以我們只要讓子類的原型等於父類的例項即可

  • Child.prototype = new Parent

JS中的多種繼承方式

2.特點

  • 1、父類中私有和公有的屬性方法,最後都變為子類例項公有的
  • 2、和其他語言不同的是
    • 原型繼承並不會把父類的屬性方法“拷貝”給子類,
    • 而是讓子類例項基於__proto__原型鏈找到自己定義的屬性和方法“指向/查詢”方式的

JS中的多種繼承方式

3.優缺點

  • c1.__proto__.xxx = xxx 修改子類原型(原有父類的一個例項)中的內容,內容被修改後,對子類的其他例項有影響,但是對父類的例項不會有影響

  • c1.__proto__.__proto__.xxx = xxx直接修改的是父類原型,這樣不僅會影響其它父類的例項,也影響其他子類的例項

  • JS中的重寫影響很大

第二種:CALL繼承(只能繼承父類中私有的,不能繼承父類中公共的)

function Parent() {
	this.x = 100;
}
Parent.prototype.getX = function getX() {
	return this.x;
};

function Child() {
	// 在子類建構函式中,把父類當做普通方法執行(沒有父類例項,父類原型上的那些東西也就和它沒關係了)
	// this -> Child的例項c1
	Parent.call(this); // this.x=100 相當於強制給c1這個例項設定一個私有的屬性x,屬性值100,相當於讓子類的例項繼承了父類的私有的屬性,並且也變為了子類私有的屬性 “拷貝式”
	this.y = 200;
}
Child.prototype.getY = function getY() {
	return this.y;
};

let c1 = new Child;
console.log(c1); 
複製程式碼

1.原理

  • 在子類建構函式中,把父類當作普通函式執行(沒有父類例項,父類原型上的那些東西也就和他沒有關係了)
    • 此時 parent(this) 這個 thiswindow
  • 通過 call 強制改變 this 的指向為 Child 中的 this
    • Parent.call(this)
    • 此時 this 是當前 Child 中的 this

相當於強制給例項設定了一個私有的屬性,相當於讓子類的例項繼承了父類的私有的屬性,並且也變為了子類私有的屬性“拷貝式”

2.特點

  • 只能繼承父類中私有的,公有的不行(並且是把父類私有的變成子類私有的)

我們滿意的繼承方式應該是:父類私有變為子類私有 父類公有變為子類公有

第三種:寄生組合式繼承(CALL繼承 + 另類原型繼承)

function Parent() {
	this.x = 100;
}
Parent.prototype.getX = function getX() {
	return this.x;
};

function Child() {
	Parent.call(this);
	this.y = 200;
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.getY = function getY() {
	return this.y;
};

let c1 = new Child;
console.log(c1); 
複製程式碼
  • 上面我們說了 call 繼承有個好處:能把父類私有的變成子類私有的;所以 call 繼承我們留下來

那現在我們只要想辦法讓父類中公有的也能變成子類中公有的即可;

1.原理

我們剛才的原型繼承,大體的實現了這個功能;

  • 原型繼承是建立了父類的一個例項,從而達到子類繼承父類的效果;它最終實現的核心點是:Child.prototype.__proto__ === Parent.prototype;
  • 如果說我們在原型繼承時不想要父類私有的內容,我們只需要:Child.prototype.__proto__ = Parent.prototype;
    JS中的多種繼承方式

這樣雖然能實現效果但是IE瀏覽器中不允許我們操作__proto__,那我們有什麼可以替換它呢?

// Object.create();建立一個空物件,讓其原型鏈指向obj
let obj = {
	xxx: 'xxx'
};
console.log(Object.create(obj));
複製程式碼

所以可以寫成:Child.prototype = Object.create(Parent.prototype);即可

JS中的多種繼承方式

2.缺點:

  • 由於新建立的原型沒有constructor屬性,所以我們自己手動加一個預設的;

四、ES6中的類和繼承

  • class類
class Parent {
	constructor() {
		this.x = 100;
	}
	// Parent.prototype.getX=function...
	getX() {
		return this.x;
	}
}
複製程式碼
  • 繼承
// 繼承: extends Parent(類似於寄生組合繼承)
// 注意:繼承後一定要在CONSTRUCTOR第一行加上SUPER
class Child extends Parent {
	constructor() {
		super(); //=>類似於我們之前的CALL繼承  super(100,200):相當於把Parent中的constructor執行,傳遞了100和200
		this.y = 200;
	}
	getY() {
		return this.y;
	}
}
let c1 = new Child;
console.log(c1);

// Child(); //=>Uncaught TypeError: Class constructor Child cannot be invoked without 'new'  ES6中建立的就是類,不能當做普通函式執行,只能new執行
複製程式碼

思維導圖

JS中的多種繼承方式

相關文章