JavaScript 語(yǔ)言中,生成實(shí)例對(duì)象的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù)。如
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);
但是這么寫,和傳統(tǒng)的面向?qū)ο笳Z(yǔ)言(如Java和C++)差異較大,所以ES6引入Class類這個(gè)概念,作為對(duì)象的模板。通過(guò)關(guān)鍵字class,可以定義類。基本上ES6的class類可以看做一個(gè)語(yǔ)法糖,它的絕大多數(shù)功能ES5都能實(shí)現(xiàn),只是class的寫法讓對(duì)象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z(yǔ)法而已。
構(gòu)造方法
constructor方法是類的默認(rèn)方法,通過(guò)new命令生成對(duì)象實(shí)例時(shí),自動(dòng)調(diào)用該方法。一個(gè)類必須有constructor方法,如果沒(méi)有顯式定義,一個(gè)空的constructor方法會(huì)被默認(rèn)添加。而this關(guān)鍵字則代表實(shí)例對(duì)象。這里的變量在類初始化的時(shí)候進(jìn)行賦值。
//定義類
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
使用的時(shí)候,我們直接對(duì)類使用new命令,跟構(gòu)造函數(shù)的用法完全一致。
class Bar {
doStuff() {
console.log('stuff');
}
}
var b = new Bar();
b.doStuff() // "stuff"
方法
我們也可以根據(jù)需要在類里面定義我們所需的方法。而類的所有方法都定義在類的prototype屬性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
在類的實(shí)例上面調(diào)用方法,其實(shí)就是調(diào)用原型上的方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
上面代碼中,b是B類的實(shí)例,它的constructor方法就是B類原型的constructor方法。
class的繼承
Class 可以通過(guò)extends關(guān)鍵字實(shí)現(xiàn)繼承。
class Point {
}
class ColorPoint extends Point {
}
上面代碼定義了一個(gè)ColorPoint類,該類通過(guò)extends關(guān)鍵字,繼承了Point類的所有屬性和方法。
在子類中
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調(diào)用父類的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 調(diào)用父類的toString()
}
}
我們通過(guò)super關(guān)鍵字,調(diào)用父類屬性。值得注意的是:子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇悰](méi)有自己的this對(duì)象,而是繼承父類的this對(duì)象,然后對(duì)其進(jìn)行加工。如果不調(diào)用super方法,子類就得不到this對(duì)象。
super關(guān)鍵字
super這個(gè)關(guān)鍵字,既可以當(dāng)作函數(shù)使用,也可以當(dāng)作對(duì)象使用。在這兩種情況下,它的用法完全不同。
第一種情況,super作為函數(shù)調(diào)用時(shí),代表父類的構(gòu)造函數(shù)。ES6 要求,子類的構(gòu)造函數(shù)必須執(zhí)行一次super函數(shù)。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代碼中,子類B的構(gòu)造函數(shù)之中的super(),代表調(diào)用父類的構(gòu)造函數(shù)。
注意:作為函數(shù)時(shí),super()只能用在子類的構(gòu)造函數(shù)之中,用在其他地方就會(huì)報(bào)錯(cuò)。
第二種情況,super作為對(duì)象時(shí),在普通方法中,指向父類的原型對(duì)象;在靜態(tài)方法中,指向父類。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代碼中,子類B當(dāng)中的super.p(),就是將super當(dāng)作一個(gè)對(duì)象使用。這時(shí),super在普通方法之中,指向A.prototype,所以super.p()就相當(dāng)于A.prototype.p()。
由于super指向父類的原型對(duì)象,所以定義在父類實(shí)例上的方法或?qū)傩裕菬o(wú)法通過(guò)super調(diào)用的。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
如果屬性定義在父類的原型對(duì)象上,super就可以取到。
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
ES6 規(guī)定,通過(guò)super調(diào)用父類的方法時(shí),super會(huì)綁定子類的this。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print(); //這句話等于console.log(this.x); 這里的this指向B,this.x=2
}
}
let b = new B();
b.m() // 2
由于綁定子類的this,所以如果通過(guò)super對(duì)某個(gè)屬性賦值,這時(shí)super就是this,賦值的屬性會(huì)變成子類實(shí)例的屬性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3; //賦值時(shí),super相當(dāng)于this 所以這句話可是為 this.x=3
console.log(super.x); // undefined 讀取時(shí),super指向父級(jí)A.prototype.x 不存在
console.log(this.x); // 3
}
}
let b = new B();
注意,使用super的時(shí)候,必須顯式指定是作為函數(shù):super()、還是作為對(duì)象:super. 使用,否則會(huì)報(bào)錯(cuò)。