ES6+ Class
1. 前言
上一節我們主要回顧了在 ES5 中使用構造函數的方式實現類了,并說明了如何實現類的繼承。從上一節的講解中,構造函數去實現類的繼承還是有諸多繁瑣的地方的,我們需要考慮子類和父類的關系,繼承中的細節都需要自己手動處理。本節我們將要學習 ES6 中的類的基本使用和類的繼承。在學習本節我們需要明確的是,ES6 中的類是基于現有語法的原型實現的,并沒有引入新的面相對象的模型。它的本質還是我們上節提到的構造函數,只是讓我們更加方便地使用,是基于原型的繼承的語法糖。
2. 基本用法
2.1 語法
上節我們在實現類的時候說,類不能被執行只能 new 來創建實例,當時我們是在內部手動處理的。在 ES6 中天然支持這個特性:
class Animal { }
Animal();
// Uncaught TypeError: Class constructor Animal cannot be invoked without 'new'
上面的代碼中我們定義了一個動物類,在控制臺中讓其執行,會看到如上的未捕獲的類型錯誤:意思是在沒有 new 的情況下是無法調用構造函數 Animal 的。使用 class 定義的類和使用構造函數定義類,在使用上是一樣的,只是創建類的方式不一樣。
上節我們知道如何創建實例上的屬性和原型上的屬性,那么使用 class 是怎么實現的呢?class 類提供了 constructor 方法,并在 new 的時候默認執行,所以在 constructor 函數內部在 this 上綁定實例上的屬性。而 原型上的方法則是在對象中直接添加屬性即可,實例如下:
class Animal {
constructor() {
this.type = '鳥類'
}
eat() {}
}
var a = new Animal();
console.log(a.hasOwnProperty('type')); // true
console.log(a.hasOwnProperty('eat')); // false
另外,在 ES7 中 class 還提供了一種方式在實例上綁定屬性,這種方式不需要 this,直接使用等號在 class 類中進行賦值。
class Animal {
constructor() {
this.type = '鳥類'
}
age='100'
eat() {}
}
var a = new Animal();
console.log(a.hasOwnProperty('age')); // true
需要注意的是,上面的等號賦值方式要在支持 ES7 的環境中才能執行。
2.2 get/set
當我們深入了解對象時我們就會知道屬性的 getter 和 setter ,提供了 get 和 set 兩個方法用于訪問和設置屬性。在 ES5 中有 Object.defineProperty()
方法可以對對象的屬性進行劫持,Vue2 的底層就是使用這個 API 實現的。當然 class 類其實也是一個對象,它也可以使用 get 的方式返回屬性值。如下實例:
class Animal {
constructor() {
this.type = "鳥類";
this._age = 8;
}
get a() {
return this._age;
}
set a(newValue) {
this._age = newValue;
}
}
var animal = new Animal();
console.log(animal.a); // 8
animal.a = 10;
console.log(animal.a); // 10
上面代碼中我們就使用了 get 和 set 去獲取屬性值和設置屬性值。那我們思考一個問題,set 和 get 是自有屬性還是原型上的屬性呢?其實 get 和 set 還是 class 類上的一個方法,所以是原型上的方法。
console.log(a.hasOwnProperty('a')); // false
2.3 static
ES6 提供了用于定義靜態屬性和方法的關鍵字 static
,靜態方法調用時不需要實例化該類,所以就不能通過實例去調用,但可以使用類直接去調用。
靜態方法通常用于為一個應用程序創建工具函數,下面我們來看一個長方形類,定義一個獲取長方形面積的靜態方法。
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
static getArea(r) {
return r.width * r.height;
}
}
const r = new Rectangle(5, 10);
console.log(Rectangle.getArea(r)); // 50
3. 繼承
3.1 extends
在上節構造函數中的繼承我們知道,子類的構造函數中,需要我們去手動執行父構造函數并綁定this,還需要將子類的構造函數的原型鏈執行父類的原型。ES6 中的繼承非常簡單,在創建子類時只需要使用關鍵字 extends
即可創建一個子類。
// 父類:動物
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + '會吃飯!');
}
static getAge() {
console.log('獲取' + this.name + '的年齡10歲了');
return 10;
}
}
// 子類:具體的動物——狗
class Dog extends Animal {}
上面的代碼中子類 Owl 繼承了 Animal,那這個時候我們都繼承了什么呢?從上面的學習中父類中有,this 上的屬性,原型上的方法和靜態方法。
var dog = new Dog('狗');
console.log('name:', dog.name); // name: 狗
console.log('age:', Dog.getAge()); // age: 10
dog.eat(); // 狗會吃飯!
從上面代碼打印的結果,我們知道,實例 dog 已經繼承了 Animal 上的屬性和方法。在父類中對 eat 方法的定義不明確,所以在子類中我們重寫 eat 方法。
class Dog extends Animal {
eat() {
console.log(this.name + '會吃飯!');
}
}
var dog = new Dog('狗');
dog.eat(); // 狗喜歡吃骨頭!
3.2 super
super
是 class 中的關鍵字,可以理解是父類的別名,用于調用對象的父對象上的函數。一般 super
有兩種情況:super
當做函數調用;一種是 super
當做父類的對象使用。
第一種情況下,super
關鍵字作為函數調用,它的作用是為了綁定 this。所以子類的構造函數必須執行一次 super。默認情況下,類中不寫 constructor 時,constructor 會自動執行 super, 并綁定 this 指向當前子類。
class A {}
class B extends A {
constructor() {
super();
}
}
上節中我們在創建子類時就去執行了父類并綁定了this,上面代碼中的 super 和 A.call(this)
是相同的。
第二種情況下,super 當作父類的對象來使用的,什么情況下會使用呢?當我們在子類中想使用父類的方法時可以使用 super 直接調用父類的方法即可。
class A {
getCount() {
return 7;
}
}
class B extends A {
constructor() {
super();
console.log(super.getCount()); // 7
}
}
let b = new B();
4. 小結
本節主要學習了 ES6 中 class 類的使用和相關的知識點,需要明確的是 class 類就是一個語法糖,底層還是基于現有的原型對象的繼承來實現的。所以要想深入理解 ES6 的 class 就需要對 ES5 中的構造函數有深入的理解,另外,我們可以使用 babel 進行轉譯,得到的代碼就是使用構造函數來實現的。