JavaScript物件導向—深入ES6的class
前言
在前面一篇中主要介紹了JavaScript中使用建構函式+原型鏈實現繼承,從實現的步驟來說還是比較繁瑣的。在ES6中推出的class的關鍵字可以直接用來定義類,寫法類似與其它的面嚮物件語言,但是使用class來定義的類其本質上依然是建構函式+原型鏈的語法糖而已,下面就一起來全面的瞭解一下class吧。
1.類的定義
class關鍵字定義類可使用兩種方式來定義:
class Person {} // 類宣告
const Person = class {} // 類表示式
2.類的建構函式
從上面class定義類可以發現是沒有
()
讓我們來傳遞引數的,當希望在例項化物件的給類傳遞一些引數,這個時候就可以使用到類的建構函式constructor
了。
-
每個類都可以有一個自己的
constructor
方法,注意只能有一個,如果有多個會丟擲異常;class Person { constructor(name, age) { this.name = name this.age = age } constructor() {} }
-
當通過new操作符來操作類時,就會去呼叫這個類的
constructor
方法,並返回一個物件(具體new操作符呼叫函式時的預設操作步驟在上一篇中有說明);class Person { constructor(name, age) { this.name = name this.age = age } } const p = new Person('curry', 30) console.log(p) // Person { name: 'curry', age: 30 }
3.類的例項方法
在建構函式中實現方法繼承是將其放到建構函式的原型上,而在class定義的類中,可直接在類中定義方法,最終class還是會幫我們放到其原型上,供多個例項來使用。
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + 'is eating.')
}
running() {
console.log(this.name + 'is running.')
}
}
4.類的訪問器方法
在使用
Object.defineProperty()
方法來控制物件的屬性時,在其資料屬性描述符中可以使用setter和getter函式,在class定義的類中,也是可以使用這兩個訪問器方法的。
class Person {
constructor(name, age) {
this.name = name
this._age = 30 // 使用_定義的屬性表示為私有屬性,不可直接訪問
}
get age() {
console.log('age被訪問')
return this._age
}
set age(newValue) {
console.log('age被設定')
this._age = newValue
}
}
const p = new Person('curry', 30)
console.log(p) // Person { name: 'curry', _age: 30 }
p.age // age被訪問
p.age = 24 // age被設定
console.log(p) // Person { name: 'curry', _age: 24 }
5.類的靜態方法
什麼叫類的靜態方法呢?該方法不是供例項物件來使用的,而是直接加在類本身的方法,可以使用類名點出來的方法,可以使用static關鍵字來定義靜態方法。
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
static foo() {
console.log('我是Person類的方法')
}
}
Person.foo() // 我是Person類的方法
6.類的繼承
6.1.extends關鍵字
在ES6之前實現繼承是不方便的,ES6中增加了extends關鍵字,可以方便的幫助我們實現類的繼承。
實現Student
子類繼承自Person
父類:
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + ' is eating.')
}
}
class Student extends Person {
constructor(sno) {
this.sno = sno
}
studying() {
console.log(this.name + ' is studying.')
}
}
那麼子類如何使用父類的屬性和方法呢?
6.2.super關鍵字
使用super關鍵字可以在子類建構函式中呼叫父類的建構函式,但是必須在子類建構函式中使用this或者返回預設物件之前使用super。
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + ' is eating.')
}
}
class Student extends Person {
constructor(name, age, sno) {
super(name, age)
this.sno = sno
}
studying() {
console.log(this.name + ' is studying.')
}
}
const stu = new Student('curry', 30, 101111)
console.log(stu) // Student { name: 'curry', age: 30, sno: 101111 }
// 父類的方法可直接呼叫
stu.eating() // curry is eating.
stu.studying() // curry is studying.
但是super關鍵字的用途並不僅僅只有這個,super關鍵字一般可以在三個地方使用:
-
子類的建構函式中(上面的用法);
-
例項方法中:子類不僅可以重寫父類中的例項方法,還可以通過super關鍵字複用父類例項方法中的邏輯程式碼;
class Person { constructor(name, age) { this.name = name this.age = age } eating() { console.log(this.name + ' is eating.') } parentMethod() { console.log('父類邏輯程式碼1') console.log('父類邏輯程式碼2') console.log('父類邏輯程式碼3') } } class Student extends Person { constructor(name, age, sno) { super(name, age) this.sno = sno } // 直接重寫父類eating方法 eating() { console.log('Student is eating.') } // 重寫父類的parentMethod方法,並且複用邏輯程式碼 parentMethod() { // 通過super呼叫父類方法,實現複用 super.parentMethod() console.log('子類邏輯程式碼4') console.log('子類邏輯程式碼5') console.log('子類邏輯程式碼6') } }
-
靜態方法中:用法就和例項方法的方式一樣了;
class Person { constructor(name, age) { this.name = name this.age = age } static parentMethod() { console.log('父類邏輯程式碼1') console.log('父類邏輯程式碼2') console.log('父類邏輯程式碼3') } } class Student extends Person { constructor(name, age, sno) { super(name, age) this.sno = sno } // 重寫父類的parentMethod靜態方法,並且複用邏輯程式碼 static parentMethod() { // 通過super呼叫父類靜態方法,實現複用 super.parentMethod() console.log('子類邏輯程式碼4') console.log('子類邏輯程式碼5') console.log('子類邏輯程式碼6') } } Student.parentMethod()
6.3.繼承內建類
extends關鍵字不僅可以實現繼承我們自定義的父類,還可以繼承JavaScript提供的內建類,可對內建類的功能進行擴充套件。
比如,在Array
類上擴充套件兩個方法,一個方法獲取指定陣列的第一個元素,一個方法陣列的最後一個元素:
class myArray extends Array {
firstItem() {
return this[0]
}
lastItem() {
return this[this.length - 1]
}
}
const arr = new myArray(1, 2, 3)
console.log(arr) // myArray(3) [ 1, 2, 3 ]
console.log(arr.firstItem()) // 1
console.log(arr.lastItem()) // 3
7.類的混入
何為類的混入?在上面的演示程式碼中,都只實現了子類繼承自一個父類,因為JavaScript的類只支援單繼承,不能繼承自多個類。如果非要實現繼承自多個類呢?那麼就可以引入混入(Mixin)的概念了。
看看JavaScript中通過程式碼如何實現混入效果:
// 封裝混入Animal類的函式
function mixinClass(BaseClass) {
// 返回一個匿名類
return class extends BaseClass {
running() {
console.log('running...')
}
}
}
class Person {
eating() {
console.log('eating...')
}
}
class Student extends Person {
studying() {
console.log('studying...')
}
}
const NewStudent = mixinClass(Student)
const stu = new NewStudent
stu.running() // running...
stu.eating() // eating...
stu.studying() // studying...
混入的實現一般不常用,因為引數不太好傳遞,過於侷限,在JavaScript中單繼承已經足夠用了。
8.class定義類轉ES5
上面介紹ES6中類的各種使用方法,極大的方便了我們對類的使用。我們在日常開發中編寫的ES6程式碼都是會被babel解析成ES5程式碼,為了對低版本瀏覽器做適配。那麼使用ES6編寫的類被編譯成ES5語法會是什麼樣呢?通過babel官網的試一試可以清楚的看到ES6語法轉成ES5後的樣子。
- 剛開始通過執行自呼叫函式得到一個Person建構函式;
- 定義的例項方法和類方法會分別收集到一個陣列中,便於後面直接呼叫函式進行遍歷新增;
- 判斷方法型別:如果是例項方法就新增到Person原型上,是類方法直接新增到Person上;
- 所以class定義類的本質還是通過建構函式+原型鏈,class就是一種語法糖;
這裡可以提出一個小問題:定義在constructor
外的屬性最終會被新增到哪裡呢?還是會被新增到類的例項化物件上,因為ES6對這樣定義的屬性進行了單獨的處理。
class Person {
message = 'hello world'
constructor(name, age) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + ' is eating.')
}
static personMethod() {
console.log('personMethod')
}
}
const p = new Person('curry', 30)
console.log(p) // Person { message: 'hello world', name: 'curry', age: 30 }
擴充套件:在上圖中通過通過babel轉換後的程式碼中,定義的Person函式前有一個/*#__PURE__*/
,那麼這個有什麼作用呢?
- 實際上這個符號將函式標記為了純函式,在JavaScript中純函式的特點就是沒有副作用,不依賴於其它東西,獨立性很強;
- 在使用webpack構建的專案中,通過babel轉換後的語法更有利於webpack進行
tree-shaking
,沒有使用到的純函式會直接在打包的時候被壓縮掉,達到減小包體積效果;