典型的面向對象編程語言(比如C++和Java)存在類(class)這個概念。所謂類就是對象的模板,對象是類的實例
JS中沒有類,在ES5中使用構造函數(constructor)作為對象的模板。但是ES5中實現繼承的代碼非常冗長與混亂(盜用構造函數、組合繼承、原型式繼承等等),因此在ES6中新引入了class關鍵字具有了正式定義類的能力
類(class)是ECMAScript中新的基礎性語法糖結構。其背后使用的仍然是原型和構造函數的概念
1.類定義
class Person {}
const Person = class {}
說明:
- class聲明不存在變量提升:
-
console.log(classDemo); class classDemo {}; // var_let_const.html:12 Uncaught ReferenceError: Cannot access 'classDemo' before initialization
-
- 函數受函數作用域限制,而類受塊作用域限制:
-
{ class classDemo { }; } console.log(classDemo); // var_let_const.html:16 Uncaught ReferenceError: classDemo is not defined
-
- 類名首字母同函數構造函數一樣,建議類名首字母大寫
2.類構造函數
constructor關鍵字用於在類定義塊內部創建類的構造函數。方法名constructor會告訴解釋器在使用new操作符創建類的新實例時,應該調用這個函數。
構造函數非必須,不定義相當於構造函數為空
示例:
class Person { constructor(override){ console.log("我是一個人") } }; let p1 = new Person() // 我是一個人
(1)使用new實例化類
使用new實例化的操作等於使用new調用其構造函數,new執行的操作有:
- 在內存中創建一個新對象
- 這個新對象內部的__proto__屬性賦值為構造函數的prototype屬性
- 構造函數內部的this被賦值為這個新對象(this指向了新對象)
- 執行構造函數內部的代碼
- 如果構造函數返回非空對象,則返回該對象,否則返回剛創建的新對象
說明:
- 類實例化時傳入的參數會用作構造函數的參數。如果不需要參數,則類名后面的括號是可選的
- 類構造函數與構造函數的主要區別在於:
- 調用類構造函數必須使用new操作符。不使用new則會拋出錯誤(Uncaught TypeError: Class constructor Person cannot be invoked without 'new')
- 而普通構造函數如果不使用new調用,那么就會以全局的this(window)作為內部對象
(2)把類當成特殊函數
JS中並沒有正式的類這個類型。從各方面看,ECMAScript的類就是一種特殊的函數。可以使用typeof 檢測表明它是一個函數:
class Person { }; console.log(typeof Person); // function
- 可以使用instanceof操作符檢查一個對象是不是類的實例:
1 class Person {}; 2 3 let p = new Person() 4 5 console.log(p instanceof Person); // true
- 類也可以向其他對象或者函數引用一樣把類作為參數傳遞
1 let pList = [ 2 class Person { 3 constructor ( id ){ 4 this._id = id 5 console.log("類作為參數:",this._id) 6 } 7 } 8 ] 9 10 function createIns( classDefinition, id ){ 11 return new classDefinition(id) 12 } 13 14 let foo = createIns(pList[0],1234) // 類作為參數: 1234
- 類還可以立即實例化
1 let foo = new class Person { 2 constructor(id) { 3 console.log("立即實例化對象") 4 } 5 }
3.實例、原型和類成員
每次通過new調用類標識符時,都會執行類構造函數,在這個函數內部,可以為新創建的實例(this)添加自有屬性。且構造函數執行完畢以后,仍然可以給實例繼續添加新成員。
(1)實例成員
每個實例都對應一個唯一的成員對象,即所有成員都不會在原型上共享:
1 class Person { 2 constructor(id) { 3 this.name = new String('Jack') 4 5 this.sayName = () => console.log(this.name); 6 7 this.nickNames = ['張三','李四'] 8 } 9 } 10 11 let p1 = new Person(),p2 = new Person(); 12 13 p1.sayName(); // Jack 14 p2.sayName(); // Jack 15 console.log(p1.name === p2.name); // false 16 console.log(p1.sayName === p2.sayName); // false 17 console.log(p1.nickNames === p2.nickNames); // false
(2)原型方法與訪問器
為了在實例間共享方法,類定義語法把在類塊中定義的方法作為原型方法
1 class Person { 2 constructor() { 3 // 存在這個類的不同的實例上 4 this.locate = () => console.log("instance"); 5 } 6 // 在類塊中定義的所有內容都會定義在類的原型上 7 locate() { 8 console.log("prototype"); 9 } 10 } 11 12 let p = new Person() 13 14 p.locate() // instance 15 Person.prototype.locate() // prototype
- 可以把方法定義在類構造函數中或者類塊中,但不能在類塊中給原型添加原始值或對象作為成員數據
1 class Person { 2 name: '張三' 3 } 4 // Uncaught SyntaxError: Unexpected identifier
- 類方法等同於對象屬性,因此可以使用字符串、符號或計算的值作為鍵
1 let funName = "fn02" 2 3 class Person { 4 5 fn01(){ 6 console.log("字符串屬性名"); 7 } 8 9 [funName](){ 10 console.log("變量屬性名"); 11 } 12 13 ['fn' + '03'](){ 14 console.log("計算屬性名"); 15 } 16 17 } 18 19 let p = new Person() 20 p.fn01() // 字符串屬性名 21 p.fn02() // 變量屬性名 22 p.fn03() // 計算屬性名
- 類定義也支持get與set訪問器:
1 class Person { 2 3 set name(newName){ 4 console.log("設置新值為:",newName); 5 this._name = newName 6 } 7 8 get name(){ 9 console.log("讀取到新值:",this._name); 10 return this._name 11 } 12 13 } 14 15 let p = new Person() 16 p.name = "張三" // 設置新值為: 張三 17 p.name // 讀取到新值: 張三
(3)非函數原型和類成員
類定義並不顯式支持在原型或類上添加成員數據,但是可以在類定義外部手動添加:
1 class Person { 2 3 sayName() { 4 console.log(`${ Person.greeting }${ this.name }`); 5 } 6 7 } 8 // 在類上定義數據成員 9 Person.greeting = ' 我的名字是' 10 // 在原型上定義數據成員 11 Person.prototype.name = '張三' 12 13 let p = new Person() 14 p.sayName() // 我的名字是張三