概述
JavaScript語言的傳統(tǒng)方法是通過構造函數,定義并生成新對象。下面是一個例子。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。上面的代碼用ES6的“類”改寫,就是下面這樣。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
上面代碼定義了一個“類”,可以看到里面有一個constructor方法,這就是構造方法,而this關鍵字則代表實例對象。也就是說,ES5的構造函數Point,對應ES6的Point類的構造方法。
Point類除了構造方法,還定義了一個toString方法。注意,定義“類”的方法的時候,前面不需要加上function這個關鍵字,直接把函數定義放進去了就可以了。另外,方法之間不需要逗號分隔,加了會報錯。
(1)在 class 類上添加的屬性都是在原型 prototype 上添加的
(2)new 實例的時候其實就是調用構造函數這個方法
(3)類的本質其實就是一個函數
(4)類中的this 指向實例對象
(5)添加的私有屬性都在構造函數中添加
(6)每個構造方法都會默認返回實例對象this,如果人為改變 return 返回值,返回基本數據類型 字符串、數字、布爾等,不會改變return this 的值;如果返回應引用數據類型 對象 數組,那么return this 就會失效,返回你返回的結果
(7) 類相當于實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱為“靜態(tài)方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
上面代碼中,Foo類的classMethod方法前有static關鍵字,表明該方法是一個靜態(tài)方法,可以直接在Foo類上調用(Foo.classMethod()),而不是在Foo類的實例上調用。如果在實例上調用靜態(tài)方法,會拋出一個錯誤,表示不存在該方法。
注意,如果靜態(tài)方法包含this關鍵字,這個this指的是類,而不是實例
(8)父類的靜態(tài)方法,可以被子類繼承。可以直接調用 。也是可以從super對象上調用的。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
(8)Class 可以通過 extends 關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多
class Animate {
constructor() {
// 默認返回實例對象 this
}
}
class Dog extends Animate {
constructor() {
super()
}
}
子類必須在 constructor 方法中調用super方法,否則新建實例時會報錯,子類就得不到 this 對象。這是因為子類自己的 this 對象,必須先通過父類的構造函數完成塑造,得到與父類同樣的實例屬性和方法,然后再對其進行加工。如果不調用 super 方法,子類就得不到this對象。
ES5 的繼承,實質是先創(chuàng)造子類的實例對象 this,然后再將父類的方法添加到 this 上面(Parent.apply(this))。ES6 的繼承機制完全不同,實質是先將父類實例對象的屬性和方法,加到 this上面(所以必須先調用super方法),然后再用子類的構造函數修改 this。
class Animate {
constructor(x, y) {
// 默認返回實例對象 this
}
}
class Dog extends Animate {
constructor(x, y, z) {
this.z = z // ReferenceError
super(x, y) // this 只能在super() 方法調用之后再使用
this.z = z
}
}
如果在子類中也寫入 num 方法,和父類中的方法重名,這樣就會覆蓋父類的 num 方法
class Animate {
constructor() {
// 默認返回實例對象 this
}
num() {
console.log('我是父類的num方法')
}
}
class Dog extends Animate {
constructor() {
super()
}
num() {
console.log('我是子類的num方法')
}
}
var dog = new Dog()
dog.num() // 我是子類的num方法
如果不想覆蓋而是想引用父類的 num 方法,那么就在子類的 num 方法中通過 super 來調用父類的 num 方法,super.num()
(9)super 關鍵字詳細解讀
super這個關鍵字,既可以當作函數使用,也可以當作對象使用。在這兩種情況下,它的用法完全不同。
1.第一種情況,super作為函數調用時,代表父類的構造函數。ES6 要求,子類的構造函數必須執(zhí)行一次super函數。
class A {}
class B extends A {
constructor() {
super();
}
}
···
上面代碼中,子類B的構造函數之中的super(),代表調用父類的構造函數。這是必須的,否則 JavaScript 引擎會報錯。
注意,super雖然代表了父類A的構造函數,但是返回的是子類B的實例,即super內部的this指的是B的實例,因此super()在這里相當于A.prototype.constructor.call(this)。
作為函數時,super()只能用在子類的構造函數之中,用在其他地方就會報錯。(就是只能寫在繼承類的constructor里面)
2.第二種情況,super作為對象時,在普通方法中,指向父類的原型對象;在靜態(tài)方法中,指向父類。
```javascript
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調用的。