和其它面向對象編程語言同樣,ES6 正式定義了 class 類以及 extend 繼承語法糖,而且支持靜態、派生、抽象、迭代、單例等,並且根據 ES6 的新特性衍生出不少有趣的用法。
基本全部面向對象的語言都支持類的封裝與繼承,那什麼是類?編程
類是面向對象程序設計的基礎,包含數據封裝、數據操做以及傳遞消息的函數。類的實例稱爲對象。segmentfault
ES5 以前經過函數來模擬類的實現以下:數組
// 構造函數 function Person(name) { this.name = name; } // 原型上的方法 Person.prototype.sayName = function(){ console.log(this.name); }; // new 一個實例 var friend = new Person("Jenny"); friend.sayName(); // Jenny console.log(friend instanceof Person); // true console.log(friend instanceof Object); // true
總結來講,定義一個類的思路以下:瀏覽器
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("Jenny"); friend.sayName(); // Jenny console.log(friend instanceof Person); // true console.log(friend instanceof Object); // true
上面的例子中class
定義的類與自定義的函數模擬類功能上貌似沒什麼不一樣,但本質上還有很大差別的:函數
類和函數同樣,是JavaScript的一等公民(能夠傳入函數、從函數返回、賦值),而且注意到類與對象字面量還有更多類似之處,這些特色能夠擴展出類更靈活的定義與使用。this
對象的屬性有數據屬性和訪問屬性,類中也能夠經過get
、set
關鍵字定義訪問器屬性:prototype
class Person { constructor(name) { this.name = name; } get value () { return this.name + this.age } set value (num) { this.age = num } } let friend = new Person("Jenny"); // 調用的是 setter friend.value = 18 // 調用的是 getter console.log(friend.value) // Jenny18
相似 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("Jenny"); // 方法 friend.sayNameDefault(); // Jenny // 訪問器屬性 friend.sayName = 'lee' console.log(friend.sayName) // lee
想進一步熟悉對象新特性可參考: 【ES6】對象的新功能與解構賦值
ES6 中經常使用的集合對象(數組、Set/Map集合)和字符串都是可迭代對象,若是類是用來表示值這些可迭代對象的,那麼定義一個默認迭代器會更有用。code
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
循環和展開運算符(...)等迭代功能。
對以上迭代器內容感到困惑的可參考: 【ES6】迭代器與可迭代對象
類做爲"一等公民」能夠當參數使用傳入函數中,固然也能夠從函數中返回:
function createClass(className, val) { return new className(val) } let person = createClass(Person,'Jenny') console.log(person) // Person { name: 'Jenny' } console.log(typeof person) // object
使用類語法建立單例的方式經過new當即調用類表達式:
let singleton = new class { constructor(name) { this.name = name; } }('Jenny') console.log(singleton.name) // Jenny
這裏先建立匿名類表達式,而後 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()
。
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。
目前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
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("Jenny"); // 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
能夠看出派生類依然可使用基類的靜態方法。
推薦閱讀《深刻理解ES6》