目錄
讓一個構造函數繼承另一個構造函數,是常見的需求。ES5 是通過修改原型鏈實現繼承,ES6中
Class
可以通過extends
關鍵字實現繼承,這比 ES5 的方法要方便很多。1.ES5的繼承
1.1 構造函數的繼承
在ES5中實現繼承,分兩步:
- 在子類的構造函數中調用父類的構造函數;
function Sub(value) {
Super.call(this);
this.prop = value;
}
- 讓子類的原型指向父類的原型,子類就會繼承父類原型。
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';
下面是一個具體的例子:
// 父類
function A(x, y) {
this.x = x;
this.y = y;
}
// 父類原型添加方法
A.prototype.move = function(x, y) {
this.x = x;
this.y = y;
}
// 子類
function B(x, y, z) {
// 1.調用父類構造函數
A.call(this, x, y);
this.z = z;
}
// 2. 子類原型指向父類原型
B.prototype = Object.create(A.prototype);
B.prototype = B;
// 創建子類實例
let b = new B(0, 0, 0);
b instanceof A; // true
b instanceof B; // true
// 調用父類原型上的方法move
b.move(1, 2);
上面例子中,instanceof
運算符會對子類和父類的構造函數,都返回true
。
上面寫法是子類整體繼承父類,有時候只需要繼承單個方法,可以使用下面的寫法:
Sub.prototype.methodName = function(value) {
// 調用父類方法
Super.prototype.methodName.call(this);
// 實現自己功能
....
}
1.2 多重繼承
JavaScript 不提供多重繼承功能,即不允許一個對象同時繼承多個對象。但是,可以通過Mixin(混入),實現這個功能。
// 父類A
function A() {
this.x = "A";
}
// 父類B
function B(){
this.y = "B"
}
// 子類C
function C() {
// 1.調用父類構造
A.call(this);
B.call(this);
this. y = "C";
}
// 2. 子類C繼承父類A原型
C.prototype = Object.create(A.prototype);
// 3. 子類原型加入父類B原型
Object.assign(C.prototype, B.prototype);
C.prototype.constructor = C;
2. ES6 基於class的繼承
Class 可以通過extends
關鍵字實現繼承。
// 父類
class Super{
...
}
class Sub extends Super {
...
}
下面是一個具體例子:
// 父類
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
toString() {
return "姓名:" + this.name + " 年齡:" + this.age;
}
}
// 子類
class Student extends Person {
constructor(name, age, score) {
// 調用父類構造
super(name, age);
this.score = score;
}
toString() {
return super.toString() + " 成績:" + this.score;
}
}
let rycony = new Student("rycony", 24, 89.0);
rycony.toString(); // "姓名:rycony 年齡:24 成績:89"
上面代碼中,在子類構造函數constructor
通過super()
調用父類構造方法,在子類原型方法toString
通過super.toString()
調用父類原型上的toString
方法。
ES5 的繼承實質上是:先創造子類的實例對象this
,然後再將父類的方法添加到this
上面(Super.call(this)
)。
ES6 的繼承實質上是:先將父類實例對象的屬性和方法,加到this
上面,然後再用子類的構造函數修改this
。
如果子類沒有定義constructor
方法,這個方法會被默認添加。
// 父類A
class A {}
// 子類B繼承父類A
class B extends A {}
// 相當於
class B extends A {
constructor(...args) {
super(...args);
}
}
子類的構造函數中,只有調用
super
之後,纔可以使用this
關鍵字,否則會報錯。因爲**子類實例的構建,基於父類實例**。
// 父類A
class A {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
// 子類(先使用this)
class B extends A {
constructor(x, y, z) {
this.z = z;
super(x, y);
}
}
new B(1, 2, 3); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
// 子類(先使用super調用父類構造,再使用this)
class C extends A {
constructor(x, y, z) {
super(x, y);
this.z = z;
}
}
new C(1, 2, 3); // C {x: 1, y: 2, z: 3}
上面例子中,類B
先使用this
,會報錯。
子類通過extends
繼承父類,連父類的靜態方法也會被繼承。
class M {
static print() {
console.info("你好,世界!");
}
}
class N extends M {
}
N.print(); // 你好,世界!
上面例子中,子類N
繼承了父類M
,父類M
的靜態方法print
也會被子類N
繼承,通過N.print()
也能調用父類的靜態方法print()
。
3. Object.getPropertyOf()
Object.getPrototypeOf
方法可以用來從子類上獲取父類。
class A {}
class B extends A{}
Object.getPropertyOf(B) === A; // true
4. super 關鍵字
super
關鍵字,既可以當作函數使用,也可以當作對象使用。
4.1 super作爲函數調用
super
作爲函數調用時,代表父類的構造函數。ES6 要求,子類的構造函數必須執行一次super
函數。
class A {}
class B extends A{
constructor() {
this.x = "jidi";
}
}
new B(); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
上面例子中,子類B
繼承父類A
,但是在子類B
構造函數中沒有通過super
調用父類構造函數,會報錯。
super
雖然代表了父類的構造函數,但是返回的是子類的實例,即super
內部的this
指的是子類的實例,因此super()
相當於Super.prototype.constructor.call(this)
。
class A {
constructor() {
console.info(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A(); // A
new B(); // B
上面例子中,new.target.name
指向當前正在執行的函數,在子類B
的構造函數通過super()
調用父類構造函數,指向的是子類B
的構造函數,即super()
內部的this
指向子類實例。
super
作爲函數調用,只能用在子類構造函數中,否則會報錯。
class A {}
class B extends A {
print() {
super(); // SyntaxError: 'super' keyword unexpected here
}
}
4.2 super作爲對象調用
super
作爲對象調用,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。
class A {
constructor(x, y) {
this.x = x;
this.y = y;
}
m() {
return "jidi";
}
static n() {
return 23;
}
}
class B extends A{
constructor(x, y) {
super(x, y);
}
mm() {
return super.m(); // super指向父類原型對象
}
static nn() {
return super.n(); // super指向父類
}
}
let x = new B(1,2);
x.mm(); // "jidi"
B.nn(); // 23
上面例子中,在子類B
方法mm
中,通過super.m()
調用父類A
的方法m
,此時super
指向父類原型對象;在子類靜態方法nn
中,通過super.n()
調用父類靜態方法n
,此時,super
執行父類。(上面例子中,子類的兩個方法其實都沒有意義,因爲子類繼承父類,會繼承父類原型上的方法和父類靜態方法,這裏只是作爲例子使用)。
在普通方法中,super
指向父類的原型對象,定義在父類實例上的方法或屬性,是無法通過super
調用的。
class A {
x = "jidi";
constructor(value) {
this.y = value;
}
}
class B extends A {
constructor(value) {
super(value);
}
print() {
console.info(super.x);
}
}
new B(23).print(); // undefined
上面例子中,子類B
繼承父類A
,在方法print
中通過super.x
想調用父類A
的實例屬性x
,結果爲undefined
。
如果屬性定義在父類原型上,super
可以獲取到屬性。
class A { }
A.prototype.x = "jidi";
class B extends A{
y() {
return super.x; // 調用父類原型對象屬性x
}
}
new B().y(); // "jidi"
上面例子中,通過super.x
就能調用到屬性x
,因爲x
定義在父類的原型對象上面。
在子類普通方法中通過super
調用父類的方法時,方法內部的this
指向當前的子類實例。
class A {
constructor() {
this.name = "jidi";
}
print(){
console.info(this.name);
}
}
class B extends A{
name = "rycony";
m() {
super.print(); // super調用父類方法,方法內部this指向當前子類實例
}
}
let b = new B();
b.m(); // "rycony"
上面例子中,子類B
方法m()
中通過super.print()
調用了父類A
的print()
方法,此時print()
方法內部的this
指向當前子類實例。
通過super
對某個屬性賦值,賦值的屬性會變成子類實例的屬性。
class A{
constructor(){
this.name = "jidi";
}
}
class G extends A{
constructor(){
super();
this.age = 21;
super.age = 23; // 通過super給屬性賦值
}
getAge(){
super.age = 33;
console.info(super.age)
return this.age;
}
}
x = new G(); // {name: "jidi", age: 23}
x.getAge(); // undefined 33
上面例子中,子類G
在構造方法和普通方法getAge
中通過super.age
爲屬性age
賦值,賦值的屬性age
會成爲子類實例的屬性,相當於調用this.age
賦值。但是,當通過super.age
讀取屬性時,讀取的是父類原型對象的屬性,相當於A.prototype.age
,所以爲undefined
。
在子類的靜態方法中通過super
調用父類的方法時,方法內部的this
指向當前的子類。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
上面例子中,靜態方法B.m
裏面,super.print
指向父類A
的靜態方法。這個方法裏面的this
指向的是子類B
,不是子類B
的實例。
使用super
關鍵字,必須顯式指定是作爲函數、還是作爲對象調用,否則會報錯。
class A {}
class B extends A{
m(){
console.log(super); // SyntaxError: 'super' keyword unexpected here
}
}
5. 原生構造函數
ES6中的原生構造函數有:
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
ES6 允許繼承原生構造函數自定義子類。
class MyFirstArray extends Array {
constructor(...args){
super(...args);
}
getName() {
return "jidi";
}
}
let x = new MyFirstArray();
x[0] = 1;
x[1] = 2;
x.length; // 2
x.getName(); // "jidi"