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