詳解ES6中的class

文章首發於 我的博客前端

目錄

  • class
  • 靜態方法
  • 靜態屬性
  • 繼承
  • super

class

class是一個語法糖,其底層仍是經過 構造函數 去建立的。因此它的絕大部分功能,ES5 均可以作到。新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。git

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function() {
    return this.name;
}

const xiaoming = new Person('小明', 18);
console.log(xiaoming);
複製代碼

上面代碼用ES6class實現,就是下面這樣es6

class Person {
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }
  
    sayName() {
      return this.name;
    }
}
const xiaoming = new Person('小明', 18)
console.log(xiaoming);
// { name: '小明', age: 18 }

console.log((typeof Person));
// function
console.log(Person === Person.prototype.constructor);
// true
複製代碼

constructor方法,這就是構造方法,this關鍵字表明實例對象。 類的數據類型就是函數,類自己就指向構造函數。github

定義類的時候,前面不須要加 function, 並且方法之間不須要逗號分隔,加了會報錯。編程

類的全部方法都定義在類的prototype屬性上面。微信

class A {
    constructor() {}
    toString() {}
    toValue() {}
}
// 等同於
function A () {
    // constructor
};
A.prototype.toString = function() {};
A.prototype.toValue = function() {};
複製代碼

在類的實例上面調用方法,其實就是調用原型上的方法。markdown

let a = new A();
a.constructor === A.prototype.constructor // true
複製代碼

constructor 方法

constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。函數

class A {
}

// 等同於
class A {
  constructor() {}
}
複製代碼

constructor方法默認返回實例對象(即this),徹底能夠指定返回另一個對象。oop

class A {
  constructor() {
      return Object.create(null);
  }
}

console.log((new A()) instanceof A);
// false
複製代碼

類的實例

實例的屬性除非顯式定義在其自己(即定義在this對象上),不然都是定義在原型上(即定義在class上)。學習

注意:

  1. class不存在變量提高
new A(); // ReferenceError
class A {}
複製代碼

由於 ES6 不會把類的聲明提高到代碼頭部。這種規定的緣由與繼承有關,必須保證子類在父類以後定義。

{
  let A = class {};
  class B extends A {}
}
複製代碼

上面的代碼不會報錯,由於 B繼承 A的時候,A已經有了定義。可是,若是存在 class提高,上面代碼就會報錯,由於 class 會被提高到代碼頭部,而let命令是不提高的,因此致使 B 繼承 A 的時候,Foo尚未定義。

  1. this的指向 類的方法內部若是含有this,它默認指向類的實例。可是,必須很是當心,一旦單獨使用該方法,極可能報錯。

靜態方法

類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。 若是在一個方法前,加上 static 關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲"靜態方法"。

class A {
    static classMethod() {
        return 'hello';
    }
}
A.classMethod();
console.log(A.classMethod());
// 'hello'

const a = new A();
a.classMethod();
// TypeError: a.classMethod is not a function
複製代碼

A 類的classMethod 方法前有 static關鍵字,代表這是一個靜態方法,能夠在 A 類上直接調用,而不是在實例上調用 在實例a上調用靜態方法,會拋出一個錯誤,表示不存在改方法。

若是靜態方法包含this關鍵字,這個this指的是類,而不是實例。

class A {
    static classMethod() {
      this.baz();
    }
    static baz() {
      console.log('hello');
    }
    baz() {
      console.log('world');
    }
}
A.classMethod();
// hello
複製代碼

靜態方法classMethod調用了this.baz,這裏的this指的是A類,而不是A的實例,等同於調用A.baz。另外,從這個例子還能夠看出,靜態方法能夠與非靜態方法重名。

父類的靜態方法,能夠被子類繼承。

class A {
    static classMethod() {
        console.log('hello');
    }
}

class B extends A {}

B.classMethod() // 'hello'
複製代碼

靜態屬性

靜態屬性指的是 Class 自己的屬性,即Class.propName,而不是定義在實例對象(this)上的屬性。 寫法是在實例屬性的前面,加上static關鍵字。

class MyClass {
  static myStaticProp = 42;

  constructor() {
    console.log(MyClass.myStaticProp); // 42
  }
}
複製代碼

繼承

Class 能夠經過extends關鍵字實現繼承

class Animal {}
class Cat extends Animal { };
複製代碼

上面代碼中 定義了一個 Cat 類,該類經過 extends關鍵字,繼承了 Animal 類中全部的屬性和方法。 可是因爲沒有部署任何代碼,因此這兩個類徹底同樣,等於複製了一個Animal類。 下面,咱們在Cat內部加上代碼。

class Cat extends Animal {
    constructor(name, age, color) {
        // 調用父類的constructor(name, age)
        super(name, age);
        this.color = color;
    }
    toString() {
        return this.color + ' ' + super.toString(); // 調用父類的toString()
    }
}
複製代碼

constructor方法和toString方法之中,都出現了super關鍵字,它在這裏表示父類的構造函數,用來新建父類的this對象。

子類必須在 constructor 方法中調用 super 方法,不然新建實例就會報錯。 這是由於子類本身的this對象,必須先經過 父類的構造函數完成塑造,獲得與父類一樣的實例屬性和方法,而後再對其進行加工,加上子類本身的實例屬性和方法。若是不調用super方法,子類就得不到this對象。

class Animal { /* ... */ }

class Cat extends Animal {
  constructor() {
  }
}

let cp = new Cat();
// ReferenceError
複製代碼

Cat 繼承了父類 Animal,可是它的構造函數沒有調用super方法,致使新建實例報錯。

若是子類沒有定義constructor方法,這個方法會被默認添加,代碼以下。也就是說,無論有沒有顯式定義,任何一個子類都有constructor方法。

class Cat extends Animal {

}
// 等同於

class Cat extends Animal {
    constructor(...args) {
        super(...args);
    }
}
複製代碼

另外一個須要注意的地方是,es5 的構造函數在調用父構造函數前能夠訪問 this, 但 es6 的構造函數在調用父構造函數(即 super)前不能訪問 this。

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

class B extends A {
  constructor(x, y, name) {
    this.name = name; // ReferenceError
    super(x, y);
    this.name = name; // 正確
  }
}
複製代碼

上面代碼中,子類的constructor方法沒有調用super以前,就使用this關鍵字,結果報錯,而放在super方法以後就是正確的。

父類的靜態方法,也會被子類繼承。

class A {
  static hello() {
    console.log('hello world');
  }
}

class B extends A {
}

B.hello()  // hello world
複製代碼

super

super這個關鍵字,既能夠看成函數使用,也能夠看成對象使用

super做爲函數調用

super做爲函數調用時,表明父類的構造函數。ES6 要求,子類的構造函數必須執行一次super函數。

class A {}

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

子類B的構造函數之中的super(),表明調用父類的構造函數。這是必須的,不然 JavaScript 引擎會報錯。

注意,super雖然表明了父類A的構造函數,可是返回的是子類B的實例,即super內部的this指的是B的實例,所以super()在這裏至關於A.prototype.constructor.call(this)。

class A {
  constructor() {
    // new.target 指向正在執行的函數
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B
複製代碼

super()執行時,它指向的是子類B的構造函數,而不是父類A的構造函數。也就是說,super()內部的this指向的是B

super做爲對象調用

在普通方法中,指向父類的原型對象; 在靜態方法中,指向父類

super對象在普通函數中調用

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

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

let b = new B();
複製代碼

上面代碼中,子類B當中的super.p(),就是將super看成一個對象使用。這時,super在普通方法之中,指向A.prototype,因此super.p()就至關於A.prototype.p()

這裏須要注意,因爲super指向父類的原型對象,因此定義在父類實例上的方法或屬性,是沒法經過super調用的。

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined
複製代碼

上面代碼中,p是父類A實例的屬性,super.p就引用不到它。

若是屬性定義在父類的原型對象上,super就能夠取到。

class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2
  }
}

let b = new B();
複製代碼

上面代碼中,屬性x是定義在A.prototype上面的,因此super.x能夠取到它的值。

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.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

const child = new Child();
child.myMethod(2); // instance 2
複製代碼

上面代碼中,super在靜態方法之中指向父類,在普通方法之中指向父類的原型對象。

另外,在子類的靜態方法中經過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指向父類的靜態方法。這個方法裏面的this指向的是B,而不是B的實例。

總結

  • class是一個語法糖,其底層仍是經過 構造函數 去建立的。
  • 類的全部方法都定義在類的prototype屬性上面。
  • 靜態方法:在方法前加static,表示該方法不會被實例繼承,而是直接經過類來調用。
  • 靜態屬性:在屬性前加static,指的是 Class 自己的屬性,而不是定義在實例對象(this)上的屬性。
  • es5 的構造函數在調用父構造函數前能夠訪問 this, 但 es6 的構造函數在調用父構造函數(即 super)前不能訪問 this。
  • super
    • 做爲函數調用,表明父類的構造函數
    • 做爲對象調用,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。

再來幾道題檢查一下

1. 下面代碼輸出什麼

class Person {
  constructor(name) {
    this.name = name
  }
}

const member = new Person("John")
console.log(typeof member)
複製代碼

答案:object

解析: 類是構造函數的語法糖,若是用構造函數的方式來重寫Person類則將是:

function Person() {
  this.name = name
}
複製代碼

經過new來調用構造函數,將會生成構造函數Person的實例,對實例執行typeof關鍵字將返回"object",上述狀況打印出"object"。

2. 下面代碼輸出什麼

class Chameleon {
  static colorChange(newColor) {
    this.newColor = newColor
    return this.newColor
  }

  constructor({ newColor = 'green' } = {}) {
    this.newColor = newColor
  }
}

const freddie = new Chameleon({ newColor: 'purple' })
freddie.colorChange('orange')
複製代碼

答案:TypeError

解析: colorChange 是一個靜態方法。靜態方法被設計爲只能被建立它們的構造器使用(也就是 Chameleon),而且不能傳遞給實例。由於 freddie 是一個實例,靜態方法不能被實例使用,所以拋出了 TypeError 錯誤。

3.下面代碼輸出什麼

class Person {
  constructor() {
    this.name = "Lydia"
  }
}

Person = class AnotherPerson {
  constructor() {
    this.name = "Sarah"
  }
}

const member = new Person()
console.log(member.name)
複製代碼

答案:"Sarah"

解析: 咱們能夠將類設置爲等於其餘類/函數構造函數。 在這種狀況下,咱們將Person設置爲AnotherPerson。 這個構造函數的名字是Sarah,因此新的Person實例member上的name屬性是Sarah。

其餘

最近發起了一個100天前端進階計劃,主要是深挖每一個知識點背後的原理,歡迎關注 微信公衆號「牧碼的星星」,咱們一塊兒學習,打卡100天。

相關文章
相關標籤/搜索