1. 程式人生 > >你真的瞭解[super ]關鍵字嗎?

你真的瞭解[super ]關鍵字嗎?

image

前言

此篇文章是看了阮老師的es6教程,看到super關鍵字的時候覺得有必要總結梳理一下,原文還是參考 ECMAScript 6入門

正文

super 這個關鍵字,既可以當作函式使用,也可以當作物件使用。

1.當作函式使用

super作為函式呼叫時,代表父類的建構函式。ES6 要求 ,子類的建構函式必須執行一次super函式。

即作為 函式 使用時下面的程式碼時 固定使用套路

class A {
   constrctor(){
   }
}

class B extends A {
   constructor() {
     super();
   }
}
複製程式碼

上面程式碼中的super()代表的誰??

子類 B 的建構函式中的super(),代表 B呼叫父類 A 的建構函式執行。

注意⚠️: 雖然這裡的 super()代表了的 父類 A的建構函式,但是返回的卻是 子類 B 的例項,即 super()內部的this 指的是 B, 因此 super() 在這裡相當於 A.prototype.constructor.call(this)

用一個大栗子來證明我的論點:

class A {
    constrctor(){
        console.log(new.target.name)
    }
}

class B extends A{
    constructor
(){ super() } } new A() // A new B() // B 複製程式碼

在這段熱氣騰騰的程式碼中, new.target 指向當前正在執行的函式。

囉嗦一下:

new是從建構函式生成的例項物件的命令。ES6 為 new 命令引入了一個 new.target 屬性,該屬性一般用在建構函式之中,返回new命令作用於的那個建構函式。

可以看到,在 super() 執行時,它指向的是子類 B 的建構函式,而不是父類 A 的建構函式。即 super() 內部的 this 指向的是 B

還有一點需要注意 ⚠️ :

作為函式時,super()只能用在子類的建構函式中,用在其他地方報錯。

// 錯誤寫法
class A {}

class B extends A {
   m() {
     super(); // 報錯
  }
}
複製程式碼

總結一下:

當super 作為函式的時候需要注意以下三點:

  1. 子類的建構函式必須執行一次super函式。
// 再次重申,這是固定寫法
class A {
 constructor() {}
}

class B extends A {
  constructor() {
    super();// 這裡表示 A.constructor()
  }
}
複製程式碼
  1. 子類的建構函式中的super()代表的是 子類呼叫父類的建構函式執行,這時候 super()中的this,即 父類.constructor 中的 this ,指向的是子類。

3.super()只能用在子類的建構函式之中,用在其他地方就會報錯。

2.super作為物件時

super 作為物件時,在普通方法中,指向父類的原型物件,在靜態方法中指向父類。

2.1 super作為物件在普通方法中應用

上程式碼:

class A {
    p(){
        return 2
    }
}

class B extends A {
    constrctor(){
        super();
        console.log(super.p());//2
    }
}

let b = new B();

複製程式碼

我們發現 在子類 Bsuper.p()執行結果為2。我們猜測這裡的supsuper.p === A.prototype.p,驗證一下發現結果是 true

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p===A.prototype.p); // true
  }
}

let b = new B();
複製程式碼

也就是說這個時候 super 在普通的方法中,指向的是 A.prototype即子類中的super指向父類的原型物件。

⚠️注意點 1. :由於子類中的super指向父類的原型物件,所以定義在父類例項上的方法或屬性,是無法通過super呼叫的。

class A {
   constructor() {
    this.s = 3;
   }
}

A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2  這裡可以獲取到父類原型上的x屬性
  }
  
  get m(){
    return super.s
  }
}

let b = new B();
console.log(b.m)// undefined  不能獲取到定義在父類例項上的s屬性
複製程式碼

通過上面的程式碼我們發現:

子類 B 可以通過super獲取到父類定義在原型上的屬性,但是定義在父類 A的例項上的屬性,無法獲取到。

注意點2⚠️:this指向

ES6 規定,在子類普通方法中通過 super 呼叫父類的方法時,方法內部的 this 指向當前的子類例項。

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();// 這裡等同於 A.proptotype.print()
  }
}

let b = new B();
b.m() // 2
複製程式碼

上面程式碼中,super.print() 雖然呼叫的是A.prototype.print(),但是 A.prototype.print() 內部的this指向子類B的例項,導致輸出的是2,而不是1。也就是說,實際上執行的是 super.print.call(this)

注意點3⚠️:通過super進行賦值操作

由於 this 指向子類例項,所以如果通過 super 對某個屬性賦值,這時super就是this,賦值的屬性會變成子類例項的屬性。

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;// 此時的 super 就是 b
    console.log(super.x); // undefined 等用於是 A.prototype.x
    console.log(this.x); // 3
  }
}

let b = new B();

複製程式碼

通過這兩段程式碼我們發現了一個問題,當我通過 super 取值的時候取的是父類的原型上屬性,但是當我通過 super 賦值的時候這時候 super 指向的是子類的例項。

總結一下:

通過上面的三個栗子得出, super作為物件在普通方法中應用時:

  1. 子類中的super指向父類的原型物件,所以定義在父類例項上的方法或屬性,是無法通過super呼叫的。
  2. 在子類普通方法中通過 super 呼叫父類的方法時,方法內部的 this 指向當前的子類例項。
  3. 通過 super 對某個屬性賦值,這時super就是this,賦值的屬性會變成子類例項的屬性。

2.2 super作為物件在靜態方法中應用

如果 super 作為物件,用在靜態方法之中,這時 super 將指向父類,而不是父類的原型物件。

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
  // 此時的 super 指的是父類,Parent
    super.myMethod(msg);
  }

  myMethod(msg) {
  // 普通函式 此時 super 是指 Parent.prototype
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2

複製程式碼

上面程式碼中,super在靜態方法之中指向父類,在普通方法之中指向父類的原型物件。

注意點1 ⚠️

在子類的靜態方法中通過 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();// A.print 中的this指向當前的子類
  }
}

B.x = 3;
B.m() // 3

複製程式碼

上面程式碼中,靜態方法 B.m 裡面,super.print指向父類的靜態方法。這個方法裡面的 this 指向的是 B,而不是 B 的例項。

總結一下:

在子類的靜態方法中通過super呼叫父類的方法時,方法內部的this指向當前的子類,而不是子類的例項。

總之

  1. super作為函式,super()代表的就是父類建構函式,裡面的this就是類的例項。
  2. super作為物件,在普通的方法中super.的語法指向的是父類的原型物件,在靜態方法中使用super.語法的話就去父類的靜態方法中找就行了。至於this指向問題,記住一點:如果在一個方法前,加上static關鍵字,就表示該方法不會被例項繼承,而是直接通過類來呼叫,所以靜態方法中的this只會指類而不是例項。