紅寶書4-第八章對象、類與面向對象編程(6)


雖然 ECMAScript 6 類表面上看起來能夠支持正式的面向對象編程,但實際上它背後使用的仍然是原型和構造函數的概念。 header

1.類定義

與函數類型類似,定義類也有兩種主要方式:類聲明類表達式。這兩種方式都使用 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

2.類構造函數

  1. constructor 關鍵字用於在類定義塊內部建立類的構造函數。
  2. 方法名 constructor 會告訴解釋器在使用 new 操做符建立類的新實例時,應該調用這個函數
  3. 構造函數的定義不是必需的不定義構造函數至關於將構造函數定義爲空函數

1. 實例化

使用 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 調用)。所以,實例化以後能夠在實例上引用它。

2. 把類當成特殊函數

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 {}

3.實例、原型和類成員

類的語法能夠很是方便地定義應該存在於實例上的成員、應該存在於原型上的成員,以及應該存在於類自己的成員。

1. 實例成員

每次經過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

2. 原型方法與訪問器

爲了在實例間共享方法,類定義語法把在類塊中定義的方法做爲原型方法。

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

3. 靜態類方法

能夠在類上定義靜態方法。這些方法一般用於執行不特定於實例的操做,也不要求存在類的實例。與原型成員相似,靜態成員每一個類上只能有一個

靜態類成員在類定義中使用 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 {}

4. 非函數原型和類成員

雖然類定義並不顯式支持在原型或類上添加成員數據,但在類定義外部,能夠手動添加:

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

在這裏插入圖片描述

5. 迭代器與生成器方法

類定義語法支持在原型和類自己上定義生成器方法:

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
相關文章
相關標籤/搜索