1. 程式人生 > >深入解析ES6 更易於繼承的類語法的使用

深入解析ES6 更易於繼承的類語法的使用

和其它面向物件程式語言一樣,ES6 正式定義了 class 類以及 extend 繼承語法糖,並且支援靜態、派生、抽象、迭代、單例等,而且根據 ES6 的新特性衍生出很多有趣的用法。

一、類的基本定義

基本所有面向物件的語言都支援類的封裝與繼承,那什麼是類? 類是面向物件程式設計的基礎,包含資料封裝、資料操作以及傳遞訊息的函式。類的例項稱為物件。 ES5 之前通過函式來模擬類的實現如下:

// 建構函式
function Person(name) {
 this.name = name;
}
// 原型上的方法
Person.prototype.sayName = function(){
 console.log(this.name);
};
// new 一個例項
var friend = new Person("前端全棧開發交流圈:864305860");
 
friend.sayName(); // 前端全棧開發交流圈:864305860
console.log(friend instanceof Person);  // true
console.log(friend instanceof Object);  // true

總結來說,定義一個類的思路如下:

  • 1.需要建構函式封裝資料
  • 2.在原型上新增方法操作資料,
  • 3.通過New建立例項

ES6 使用class關鍵字定義一個類,這個類有特殊的方法名[[Construct]]定義建構函式,在 new 建立例項時呼叫的就是[[Construct]],示例如下:

/*ES6*/
// 等價於 let Person = class {
class Person {
 // 建構函式
 constructor(name) {
  this.name = name;
 }
 // 等價於Person.prototype.sayName
 sayName() {
  console.log(this.name);
 }
}
 
console.log(typeof Person);  // function
console.log(typeof Person.prototype.sayName);  // function
 
let friend = new Person("前端全棧開發交流圈:864305860");
 
friend.sayName(); // 前端全棧開發交流圈:864305860
console.log(friend instanceof Person);  // true
console.log(friend instanceof Object);  // true

上面的例子中class定義的類與自定義的函式模擬類功能上貌似沒什麼不同,但本質上還有很大差異的:

  • 函式宣告可以被提升,但是class類宣告與let類似,不能被提升;
  • 類宣告自動執行在嚴格模式下,“use strict”;
  • 類中所有方法都是不可列舉的,enumerable 為 false。

二、更靈活的類

類和函式一樣,是JavaScript的一等公民(可以傳入函式、從函式返回、賦值),並且注意到類與物件字面量還有更多相似之處,這些特點可以擴展出類更靈活的定義與使用。 2.1 擁有訪問器屬性 物件的屬性有資料屬性和訪問屬性,類中也可以通過get、set關鍵字定義訪問器屬性:

class Person {
 constructor(name) {
  this.name = name;
 }
 
 get value () {
  return this.name + this.age
 }
 set value (num) {
  this.age = num
 }
}
 
let friend = new Person("前端全棧開發交流圈:864305860");
// 呼叫的是 setter
friend.value = 18
// 呼叫的是 getter
console.log(friend.value) // 前端全棧開發交流圈:86430586018

2.2 可計算的成員名稱

類似 ES6 物件字面量擴充套件的可計算屬性名稱,類也可以用[表示式]定義可計算成員名稱,包括類中的方法和訪問器屬性:

let methodName = 'sayName'
 
class Person {
 constructor(name) {
  this.name = name;
 }
 
 [methodName + 'Default']() {
  console.log(this.name);
 }
 
 get [methodName]() {
  return this.name
 }
 
 set [methodName](str) {
  this.name = str
 }
}
 
let friend = new Person("前端全棧開發交流圈:864305860");
 
// 方法
friend.sayNameDefault(); // 前端全棧開發交流圈:864305860
// 訪問器屬性
friend.sayName = 'lee'
console.log(friend.sayName) // lee

2.3 定義預設迭代器

ES6 中常用的集合物件(陣列、Set/Map集合)和字串都是可迭代物件,如果類是用來表示值這些可迭代物件的,那麼定義一個預設迭代器會更有用。

ES6 通過給Symbol.iterator屬性新增生成器的方式,定義預設迭代器:

class Person {
 constructor(name) {
  this.name = name;
 }
 
 *[Symbol.iterator]() {
  for (let item of this.name){
   yield item
  }
 }
}
 
var abbrName = new Person(new Set(['j', 'j', 'e', 'e', 'n', 'y', 'y', 'y',]))
for (let x of abbrName) {
 console.log(x); // j e n y
}
console.log(...abbrName) // j e n y

定義預設迭代器後類的例項就可以使用for-of迴圈和展開運算子(...)等迭代功能。

2.4 作為引數的類

類作為"一等公民”可以當引數使用傳入函式中,當然也可以從函式中返回:

function createClass(className, val) {
 return new className(val)
}
 
let person = createClass(Person,'前端全棧開發交流圈:864305860')
console.log(person) // Person { name: '前端全棧開發交流圈:864305860' }
console.log(typeof person) // object

2.5 建立單例

使用類語法建立單例的方式通過new立即呼叫類表示式:

let singleton = new class {
 constructor(name) {
  this.name = name;
 }
}('前端全棧開發交流圈:864305860')
  
console.log(singleton.name) // 前端全棧開發交流圈:864305860

這裡先建立匿名類表示式,然後 new 呼叫這個類表示式,並通過小括號立即執行,這種類語法建立的單例不會在作用域中暴露類的引用。

三、類的繼承

回顧 ES6 之前如何實現繼承?常用方式是通過原型鏈、建構函式以及組合繼承等方式。 ES6 的類使用熟悉的extends關鍵字指定類繼承的函式,並且可以通過surpe()方法訪問父類的建構函式。 例如繼承一個 Person 的類:

class Friend extends Person {
 constructor(name, phone){
  super(name)
  this.phone = phone
 }
}
 
let myfriend = new Friend('lee',2233)
console.log(myfriend) // Friend { name: 'lee', phone: 2233 }

Friend 繼承了 Person,術語上稱 Person 為基類,Friend 為派生類。 需要注意的是,surpe()只能在派生類中使用,它負責初始化 this,所以派生類使用 this 之前一定要用surpe()。

3.1 繼承內建物件

ES6 的類繼承可以繼承內建物件(Array、Set、Map 等),繼承後可以擁有基類的所有內建功能。例如:

class MyArray extends Array {
}
 
let arr = new MyArray(1, 2, 3, 4),
 subarr = arr.slice(1, 3)
 
console.log(arr.length) // 4
console.log(arr instanceof MyArray) // true
console.log(arr instanceof Array) // true
console.log(subarr instanceof MyArray) // true

注意到上例中,不僅 arr 是派生類 MyArray 的例項,subarr 也是派生類 MyArray 的例項,內建物件繼承的實用之處是改變返回物件的型別。 瀏覽器引擎背後是通過[Symbol.species]屬性實現這一行為,它被用於返回函式的靜態訪問器屬性,內建物件定義了[Symbol.species]屬性的有 Array、ArrayBuffer、Set、Map、Promise、RegExp、Typed arrays。

3.2 繼承表示式的類

目前extends可以繼承類和內建物件,但更強大的功能從表示式匯出類! 這個表示式要求可以被解析為函式並具有[[Construct]]屬性和原型,示例如下:

function Sup(val) {
 this.value = val
}
 
Sup.prototype.getVal = function () {
 return 'hello' + this.value
}
 
class Derived extends Sup {
 constructor(val) {
  super(val)
 }
}
 
let der = new Derived('world')
console.log(der) // Derived { value: 'world' }
console.log(der.getVal()) // helloworld

3.3 只能繼承的抽象類

ES6 引入new.target元屬性判斷函式是否通過new關鍵字呼叫。類的建構函式也可以通過new.target確定類是如何被呼叫的。 可以通過new.target建立抽象類(不能例項化的類),例如:

class Abstract {
 constructor(){
  if(new.target === Abstract) {
   throw new Error('抽象類(不能直接例項化)')
  }
 }
}
 
class Instantiable extends Abstract {
 constructor() {
  super()
 }
}
 
// let abs = new Abstract() // Error: 抽象類(不能直接例項化)
 let abs = new Instantiable()
console.log(abs instanceof Abstract) // true

雖然不能直接使用 Abstract 抽象類建立例項,但是可以作為基類派生其它類。

四、類的靜態成員

ES6 使用static關鍵字宣告靜態成員或方法。在類的方法或訪問器屬性前都可以使用static,唯一的限制是不能用於建構函式。 靜態成員的作用是某些類成員的私有化,及不可在例項中訪問,必須要直接在類上訪問。

class Person {
 constructor(name) {
  this.name = name;
 }
 
 static create(name) {
  return new Person(name);
 }
}
 
let beauty = Person.create("前端全棧開發交流圈:864305860");
// beauty.create('lee') // TypeError

如果基類有靜態成員,那這些靜態成員在派生類也可以使用。 例如將上例的 Person 作為基類,派生出 Friend 類並使用基類的靜態方法create( ):

class Friend extends Person {
 constructor(name){
  super(name)
 }
}
 
var friend = Friend.create('lee')
console.log(friend instanceof Person) // true
console.log(friend instanceof Friend) // false

結語

感謝您的觀看,如有不足之處,