ECMAScript6中終於引入了類的特性,在此之前只能通過其他方法定義並關聯多個相似的對象,當然了,ES6中的類與其他語言中的還是不太一樣,其語法的設計實際上借鑒了JavaScript的動態性,本文檔簡單介紹一下ES6及其新特性。
類的聲明
ES6中聲明一個類,首先編寫class關鍵字,緊跟着是類的名字,其他部分的語法類似於對象字面量方法的簡寫形式,但是不需要子類的各元素之間使用逗號分隔,請看下面這段簡單的類聲明代碼:
class PersonClass{
constructor(name){
this.name = name
}
sayName(){
return this.name
}
}
let person = new PersonClass('xiaoMing')
console.log(person.sayName()) // 'xiaoMing'
console.log(person instanceof PersonType) //true
console.log(person instanceof Object) //true
console.log(typeof PersonClass) // function
console.log(typeof PersonClass.prototype.sayName) // function
對比類聲明語法定義PersonClass的行為與ES5之前用構造函數構建近似類過程相似,但是仍有很多差異:
1. 函數聲明可以被提升,而類聲明與let聲明類似,不能被提升;真正執行聲明語句之前,他們會一直存在於臨時死區中。
2. 類聲明中的所有代碼將自動運行在嚴格模式下,而且無法強行讓代碼脫離嚴格模式執行。
3. 在自定義類型,需要通過Object.defineProperty()方法手工指定某個方法為不可枚舉,而在類中,所有的方法都是不可枚舉的。
4. 每個類都有一個名為[Constructor]]的內部方法,通過關鍵字new調用方那些不含[Constructor]]的方法會導致程序拋出錯誤。
5. 使用關鍵字new以外的方式調用類的構造函數會導致程序拋出錯誤。
6. 在類中修改類名會導致程序報錯。
類不僅僅可以通過聲明實現,還可以使用類表達式 ,類表達式又分為匿名類表達式和命名類表達式,具體語法如下所示:
//匿名類表達式
let PersonClass = class{
constructor(name){
this.name = name
}
sayName(){
console.log(this.name)
}
}
//命名表達式
let PersonClass = class PersonClass2{
constructor(name){
this.name = name
}
sayName(){
console.log(this.name)
}
}
console.log(typeof PersonClass) // function
console.log(typeof PersonClass2) // undefined
兩者除了命名表達式需在關鍵字class后添加一個標識符外,並無其他差別,但是注意typeof PersonClass2 輸出的是undefined,這是因為PersonClass2只存在於類定義中,而在類的外部,其實並不存在一個名為PersonClass2的綁定。下邊我們用一段沒有使用關鍵字class的等價聲明來了解這背后的原理:
let PersonClass = (function () {
const PersonClass2 = function (name) {
if (typeof new.target === "undefined") { //類與構造函數差異的第五條
throw new Error("必須通過new關鍵字調用構造函數")
}
this.name = name
}
Object.defineProperty(PersonClass2.prototype, 'sayName', {
value: function () {
if (typeof new.target !== "undefined") { //類與構造函數差異的第四條
throw new Error("不可使用new關鍵字調用該方法")
}
console.log(this.name)
},
enumerable: false,
writable: true,
configurable: true,
})
return PersonClass2
}())
外部作用域中的PersonClasss是用let聲明的,而立即表達式中的PersonClass2是用const聲明的,這也說明了為什么可以在外部修改類名而內部卻不可修改,且PersonClass2只能在類內部使用。
類的特點
下邊簡單介紹一下類中的幾個小特性
- 一級公民類
在編程中,能被當做值使用的就成為一級公民,意味着它可以被當做參數傳給函數,可以作為函數返回值,能用來賦值給變量,JS的類也可以被當成值使用,就是一級公民類。 - 訪問屬性
自有屬性需要在類構造器中創建,而類還允許你在原型上定義訪問器屬性。為了創建一個getter ,要使用 get 關鍵字,並要與后方標識符之間留出空格;創建 setter 用相同方式,只是要換用 set 關鍵字。 - 需計算成員名
類方法與類訪問器屬性也都能使用需計算的名稱。語法相同於對象字面量中的需計算名稱:無須使用標識符,而是用方括號來包裹一個表達式。 - 生成器方法
可以使用 Symbol.iterator 來定義生成器方法,從而定義出類的默認迭代器。 - 靜態成員
ES6 類的靜態成員的創建,只要在方法與訪問器屬性的名稱前添加正式的 static 標注。你能在類中的任何方法與訪問器屬性上使用 static 關鍵字,唯一限制是不能將它用於 constructor 方法的定義。
繼承與派生類
首先我們來看下ES6中是怎么實現繼承的:
class Rectangle(){
constructor(length,width){
this.length = length
this.width = width
}
getArea(){
return this.length * this.width
}
}
class Square extends Rectangle {
constructor(length){
//等價於Rectangle.call(this,length,length)
super(length,length)
}
}
var square = new Square(3)
console.log(square.getArea()) //9
console.log(square instanceof Suqare) //true
console.log(square instanceof Rectangle) //true
ES5中Square繼承自Rectangle,為了這樣做,必須創建自Rectangle.prototype的新對象重寫Square.prototype並調用Rectangle。call()方法,這些步驟很容易讓人感到困惑,並在這里犯錯。ES6類的出現讓我們輕松實現了繼承功能,使用extends關鍵字可以指定類繼承的函數,繼承自其他類的類被稱作派生類,如果在派生類中指定了構造函數則必須要調用super(),通過調用super()方法即可訪問基類的構造函數,原型會自動調整,如果不這樣做,程序就會報錯。如果選擇不適用構造函數,則當創建新的類實例時會自動調用super()並傳入所有參數。舉個例子,一下兩個類完全相同:
class Square extends Rectangle{
//沒有構造函數
}
clas Square extends Rectangle{
constructor(...args){
super(...args)
}
}
派生類的特性
派生類除了上述的特征外,還有一些其他的特性:
- 派生類中的方法總會覆蓋基類中的同名方法。
- 如果基類中有靜態成員,那么這些靜態成員在派生類中也可用。
- 只要表達式可以被解析為一個函數並且具有[[constructor]]屬性和原型,就可以用extends進行派生。
- ES6支持內建對象的繼承。