雖然 ECMAScript 6 類表面上看起來能夠支持正式的面向對象編程,但實際上它背後使用的仍然是原型和構造函數的概念。 | header |
---|---|
與函數類型類似,定義類也有兩種主要方式:類聲明和類表達式。這兩種方式都使用 class 關鍵字加大括號:javascript
// 類聲明 class Person {} // 類表達式 const Animal = class {};
與函數表達式相似,類表達式在它們被求值前也不能引用。不過,與函數定義不一樣的是,雖然函數聲明能夠提高,但類定義不能。java
另外一個跟函數聲明不一樣的地方是,函數受函數做用域限制,而類受塊做用域限制編程
類能夠包含構造函數方法、實例方法、獲取函數、設置函數和靜態類方法,但這些都不是必需的。空的類定義照樣有效。默認狀況下,類定義中的代碼都在嚴格模式下執行。數組
與函數構造函數同樣,多數編程風格都建議類名的首字母要大寫,以區別於經過它建立的實例(好比,經過 class Foo {}建立實例 foo):ide
// 空類定義,有效 class Foo {} // 有構造函數的類,有效 class Bar { constructor() {} } // 有獲取函數的類,有效 class Baz { get myBaz() {} } // 有靜態方法的類,有效 class Qux { static myQux() {} }
類表達式的名稱是可選的。在把類表達式賦值給變量後,能夠經過 name 屬性取得類表達式的名稱字符串。但不能在類表達式做用域外部訪問這個標識符。函數
let Person = class PersonName { identify() { console.log(Person.name, PersonName.name); } } let p = new Person(); p.identify(); // PersonName PersonName console.log(Person.name); // PersonName console.log(PersonName); // ReferenceError: PersonName is not defined
使用 new 操做符實例化 Person 的操做等於使用 new 調用其構造函數。惟一可感知的不一樣之處就是,JavaScript 解釋器知道使用 new 和類意味着應該使用 constructor 函數進行實例化。測試
若是構造函數返回非空對象,則返回該對象;不然,返回剛建立的新對象。this
class Person { constructor(name) { console.log(arguments.length); this.name = name || null; } } let p1 = new Person; // 0 console.log(p1.name); // null let p2 = new Person(); // 0 console.log(p2.name); // null let p3 = new Person('Jake'); // 1 console.log(p3.name); // Jake
默認狀況下,類構造函數會在執行以後返回 this 對象。構造函數返回的對象會被用做實例化的對象,若是沒有什麼引用新建立的 this 對象,那麼這個對象會被銷燬。不過,若是返回的不是 this 對象,而是其餘對象,那麼這個對象不會經過 instanceof 操做符檢測出跟類有關聯,由於這個對象的原型指針並無被修改。prototype
class Person { constructor(override) { this.foo = 'foo'; if (override) { return { bar: 'bar' }; } } } let p1 = new Person(), p2 = new Person(true); console.log(p1); // Person{ foo: 'foo' } console.log(p1 instanceof Person); // true console.log(p2); // { bar: 'bar' } console.log(p2 instanceof Person); // false
類構造函數與構造函數的主要區別是,調用類構造函數必須使用 new 操做符。而普通構造函數若是不使用 new 調用,那麼就會以全局的 this(一般是 window)做爲內部對象。指針
調用類構造函數時若是忘了使用 new 則會拋出錯誤:
function Person() {} class Animal {} // 把 window 做爲 this 來構建實例 let p = Person(); let a = Animal(); // TypeError: class constructor Animal cannot be invoked without 'new'
類構造函數沒有什麼特殊之處,實例化以後,它會成爲普通的實例方法(但做爲類構造函數,仍然要使用 new 調用)。所以,實例化以後能夠在實例上引用它。
ECMAScript 中沒有正式的類這個類型。從各方面來看,ECMAScript 類就是一種特殊函數。聲明一個類以後,經過 typeof
操做符檢測類標識符,代表它是一個函數。
類標識符有 prototype
屬性,而這個原型也有一個 constructor 屬性
指向類自身。
與普通構造函數同樣,可使用 instanceof
操做符檢查構造函數原型是否存在於實例的原型鏈中。
類是 JavaScript 的一等公民,所以能夠像其餘對象或函數引用同樣把類做爲參數傳遞:
// 類能夠像函數同樣在任何地方定義,好比在數組中 let classList = [ class { constructor(id) { this.id_ = id; console.log(`instance ${this.id_}`); } } ]; function createInstance(classDefinition, id) { return new classDefinition(id); } let foo = createInstance(classList[0], 3141); // instance 3141
與當即調用函數表達式類似,類也能夠當即實例化:
// 由於是一個類表達式,因此類名是可選的 let p = new class Foo { constructor(x) { console.log(x); } }('bar'); // bar console.log(p); // Foo {}
類的語法能夠很是方便地定義應該存在於實例上的成員、應該存在於原型上的成員,以及應該存在於類自己的成員。
每次經過new調用類標識符時,都會執行類構造函數。在這個函數內部,能夠爲新建立的實例(this)添加「自有」屬性。至於添加什麼樣的屬性,則沒有限制。另外,在構造函數執行完畢後,仍然能夠給實例繼續添加新成員。
每一個實例都對應一個惟一的成員對象,這意味着全部成員都不會在原型上共享:
class Person { constructor() { // 這個例子先使用對象包裝類型定義一個字符串 // 爲的是在下面測試兩個對象的相等性 this.name = new String('Jack'); this.sayName = () => console.log(this.name); this.nicknames = ['Jake', 'J-Dog'] } } let p1 = new Person(), p2 = new Person(); p1.sayName(); // Jack p2.sayName(); // Jack console.log(p1.name === p2.name); // false console.log(p1.sayName === p2.sayName); // false console.log(p1.nicknames === p2.nicknames); // false p1.name = p1.nicknames[0]; p2.name = p2.nicknames[1]; p1.sayName(); // Jake p2.sayName(); // J-Dog
爲了在實例間共享方法,類定義語法把在類塊中定義的方法做爲原型方法。
class Person { constructor() { // 添加到 this 的全部內容都會存在於不一樣的實例上 this.locate = () => console.log('instance'); } // 在類塊中定義的全部內容都會定義在類的原型上 locate() { console.log('prototype'); } } let p = new Person(); p.locate(); // instance Person.prototype.locate(); // prototype
能夠把方法定義在類構造函數中或者類塊中,但不能在類塊中給原型添加原始值或對象做爲成員數據:
class Person { name: 'Jake' } // Uncaught SyntaxError: Unexpected token
類方法等同於對象屬性,所以可使用字符串、符號或計算的值做爲鍵:
const symbolKey = Symbol('symbolKey'); class Person { stringKey() { console.log('invoked stringKey'); } [symbolKey]() { console.log('invoked symbolKey'); } ['computed' + 'Key']() { console.log('invoked computedKey'); } } let p = new Person(); p.stringKey(); // invoked stringKey p[symbolKey](); // invoked symbolKey p.computedKey(); // invoked computedKey
類定義也支持獲取和設置訪問器。語法與行爲跟普通對象同樣:
class Person { set name(newName) { this.name_ = newName; } get name() { return this.name_; } } let p = new Person(); p.name = 'Jake'; console.log(p.name); // Jake
能夠在類上定義靜態方法。這些方法一般用於執行不特定於實例的操做,也不要求存在類的實例。與原型成員相似,靜態成員每一個類上只能有一個。
靜態類成員在類定義中使用 static
關鍵字做爲前綴。在靜態成員中,this 引用類自身。其餘全部約定跟原型成員同樣:
class Person { constructor() { // 添加到 this 的全部內容都會存在於不一樣的實例上 this.locate = () => console.log('instance', this); } // 定義在類的原型對象上 locate() { console.log('prototype', this); } // 定義在類自己上 static locate() { console.log('class', this); } } let p = new Person(); p.locate(); // instance, Person {} Person.prototype.locate(); // prototype, {constructor: ... } Person.locate(); // class, class Person {}
雖然類定義並不顯式支持在原型或類上添加成員數據,但在類定義外部,能夠手動添加:
class Person { sayName() { console.log(`${Person.greeting} ${this.name}`); } } // 在類上定義數據成員 Person.greeting = 'My name is'; // 在原型上定義數據成員 Person.prototype.name = 'Jake'; let p = new Person(); p.sayName(); // My name is Jake
類定義語法支持在原型和類自己上定義生成器方法:
class Person { // 在原型上定義生成器方法 *createNicknameIterator() { yield 'Jack'; yield 'Jake'; yield 'J-Dog'; } // 在類上定義生成器方法 static *createJobIterator() { yield 'Butcher'; yield 'Baker'; yield 'Candlestick maker'; } } let jobIter = Person.createJobIterator(); console.log(jobIter.next().value); // Butcher console.log(jobIter.next().value); // Baker console.log(jobIter.next().value); // Candlestick maker let p = new Person(); let nicknameIter = p.createNicknameIterator(); console.log(nicknameIter.next().value); // Jack console.log(nicknameIter.next().value); // Jake console.log(nicknameIter.next().value); // J-Dog
由於支持生成器方法,因此能夠經過添加一個默認的迭代器,把類實例變成可迭代對象:
class Person { constructor() { this.nicknames = ['Jack', 'Jake', 'J-Dog']; } *[Symbol.iterator]() { yield *this.nicknames.entries(); } } let p = new Person(); for (let [idx, nickname] of p) { console.log(nickname); } // Jack // Jake // J-Dog
也能夠只返回迭代器實例:
class Person { constructor() { this.nicknames = ['Jack', 'Jake', 'J-Dog']; } [Symbol.iterator]() { return this.nicknames.entries(); } } let p = new Person(); for (let [idx, nickname] of p) { console.log(nickname); } // Jack // Jake // J-Dog