【ES6】class的基本使用與繼承
生成例項物件的傳統方法是通過建構函式
// 先定義一個函式,強行叫它建構函式 function Point(x, y) { this.x = x; this.y = y; } // 建構函式的方法都定義在建構函式的原型上 Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; // 使用new的方式得到一個例項物件 var p = new Point(1, 2);
定義類
ES6
引入了class
(類),讓javascript
的面向物件程式設計變得更加容易清晰和容易理解。類只是基於原型的面向物件模式的語法糖。
類實際上是個“特殊的函式”,就像你能夠定義的函式表示式和函式宣告一樣,類語法有兩個組成部分:類宣告 和類表示式 。
- 類宣告
類宣告是定義類的一種方式,使用關鍵字class
,後面跟上類名,然後就是一對大括號。把類需要定義的方法放在大括號中。
//定義類 class Point { constructor(x, y) {// 定義構造方法 // this關鍵字代表例項物件 this.x = x; this.y = y; } // 定義在類中的方法不需要新增 function toString() { return '(' + this.x + ', ' + this.y + ')'; } } //定義類,可以省略 constructor class P { toString() { return '(' + this.x + ', ' + this.y + ')'; } }
- 類表示式
類表示式是定義類的另一種形式,類似於函式表示式,把一個函式作為值賦給變數。可以把定義的類賦值給一個變數,這時候變數就為類名。
class
關鍵字之後的類名可有可無,如果存在,則只能在類內部使用。
- 定義類 class後面有類名:
const People = class StdInfo { constructor(){ console.log(StdInfo);//可以打印出值,是一個函式 } } new People(); new StdInfo();//報錯,StdInfo is not defined;
- 定義類 class後面沒有類名:
const People = class { constructor(){ } } new People();
- 立即執行的類:
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }('張三'); person.sayName(); // "張三"
不存在變數提升
定義類不存在變數提升,只能先定義類後使用,跟函式宣告有區別的(函式宣告會被提升)。
//-----函式宣告------- //定義前可以先使用,因為函式宣告提升的緣故,呼叫合法。 func(); function func(){} //-----定義類------- new Foo(); // ReferenceError class Foo {}
類與建構函式
-
ES6
的類,完全可以看作建構函式的另一種寫法。類的資料型別就是函式,類本身就指向建構函式。
class Point { // ... } typeof Point // "function" Point === Point.prototype.constructor // true
-
建構函式的
prototype
屬性,在ES6
的“類”上面繼續存在; 類的所有方法都定義在類的prototype
屬性上面,Object.assign
方法可以一次向類新增多個方法 。
class Point { constructor(){ // ... } } Object.assign(Point.prototype, { toString(){}, toValue(){} });
constructor
方法
-
constructor
方法是類的預設方法,通過new
命令生成物件例項時,自動呼叫該方法。一個類必須有constructor
方法,如果沒有顯式定義,一個空的constructor
方法會被預設新增。
class Point { } // 等同於 class Point { constructor() {} }
-
一個類中只能有一個
constructor
函式,定義多個會報錯。 -
constructor
預設返回該物件例項(即this
),也可以指定返回另外一個物件。
class Foo { constructor() { return Object.create(null); } } new Foo() instanceof Foo // false
-
在一個構造方法中可以使用
super
關鍵字來呼叫一個父類的構造方法。
class A {} class B extends A { constructor() { super(); } }
類的例項物件
使用new
命令生成類的例項物件。類必須使用new
呼叫,否則會報錯。
class Point { // ... } // 報錯 var point = Point(2, 3); // 正確 var point = new Point(2, 3);
類的所有例項共享一個原型物件。
var p1 = new Point(2,3); var p2 = new Point(3,2); p1.__proto__ === p2.__proto__//true
Class
的靜態方法
-
類相當於例項的原型,所有在類中定義的方法,都會被例項繼承。如果在一個方法前,加上
static
關鍵字,就表示該方法不會被例項繼承,而是直接通過類來呼叫 ,這就稱為“靜態方法 ”。
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() // 'hello' var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function
-
如果靜態方法包含
this
關鍵字,這個this
指的是類,而不是例項。
class Foo { static bar () { this.baz(); } // 靜態方法可以與非靜態方法重名 static baz () { console.log('hello'); } baz () { console.log('world'); } } Foo.bar() // hello
- 父類的靜態方法,可以被子類繼承。
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { } Bar.classMethod() // 'hello'
-
靜態方法也是可以從
super
物件上呼叫的。
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { static classMethod() { return super.classMethod() + ', too'; } } Bar.classMethod() // "hello, too"
new.target
屬性
-
new
是從建構函式生成例項物件的命令。在建構函式之中,new.target
屬性返回new
命令作用於的那個建構函式。如果建構函式不是通過new
命令呼叫的,new.target
會返回undefined
,因此這個屬性可以用來確定建構函式是怎麼呼叫的。
function Person(name) { if (new.target !== undefined) { this.name = name; } else { throw new Error('必須使用 new 命令生成例項'); } } // 另一種寫法 function Person(name) { if (new.target === Person) { this.name = name; } else { throw new Error('必須使用 new 命令生成例項'); } } var person = new Person('張三'); // 正確 var notAPerson = Person.call(person, '張三');// 報錯
-
Class
內部呼叫new.target
,返回當前Class
。
class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); this.length = length; this.width = width; } } var obj = new Rectangle(3, 4); // 輸出 true
-
子類繼承父類時,
new.target
會返回子類。
class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); // ... } } class Square extends Rectangle { constructor(length) { super(length, length); } } var obj = new Square(3); // 輸出 false
class的繼承
-
Class
可以通過extends
關鍵字實現繼承,這比ES5
的通過修改原型鏈實現繼承,要清晰和方便很多。使用繼承的方式,子類就擁有了父類的方法。
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 呼叫父類的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 呼叫父類的toString() } }
-
如果子類中有
constructor
建構函式,則必須使用呼叫super
。如果不呼叫super
方法,子類就得不到this
物件。
class Point { /* ... */ } class ColorPoint extends Point { constructor() { } } let cp = new ColorPoint(); // ReferenceError
-
在子類的建構函式中,只有呼叫
super
之後,才可以使用this
關鍵字,否則會報錯。
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { this.color = color; // ReferenceError super(x, y); this.color = color; // 正確 } }
super
關鍵字
super
這個關鍵字,既可以當作函式使用,也可以當作物件使用。在這兩種情況下,它的用法完全不同。
-
super
作為函式呼叫時,代表父類的建構函式。ES6
要求,子類的建構函式必須執行一次super
函式。
class A { constructor() { console.log(new.target.name); } } class B extends A { constructor() { super(); } } new A() // A new B() // B
作為函式時,super()
只能用在子類的建構函式之中,用在其他地方就會報錯。
-
super
作為物件時,在普通方法中,指向父類的原型物件 ;在靜態方法中,指向父類。
class A { p() { return 2; } } class B extends A { constructor() { super(); console.log(super.p()); // 2 } } let b = new B();
上面程式碼中,子類B當中的super.p()
,就是將super
當作一個物件使用。這時,super
在普通方法之中,指向A.prototype
,所以super.p()
就相當於A.prototype.p()
。
由於super
指向父類的原型物件,所以定義在父類例項上的方法或屬性,是無法通過super
呼叫的。
參考文章
- ofollow,noindex">class的基本語法-阮一峰
- class的繼承-阮一峰
- JavaScript/">JavaScript/Reference/Classes" target="_blank" rel="nofollow,noindex">類-MDN