ES6 中的 class 是一種面向對象編程的語法糖,提供了一種簡潔的方式來定義對象的結構和行為。
JavaScript 語言中,生成實例對象的傳統方法是通過構造函數。下面是一個例子。
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 改寫,就是下面這樣:
class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return '(' + this.x + ', ' + this.y + ')';}
}
ES6 的類,完全可以看作構造函數的另一種寫法:
class Point {// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true----------------------------------------------------------------------------
class Point {constructor() {// ...}toString() {// ...}toValue() {// ...}
}
// 等同于
Point.prototype = {constructor() {},toString() {},toValue() {},
};
主要特性:
1. 聲明式語法:使用 class 關鍵字聲明類。
2. 構造函數:使用 constructor 方法初始化類實例。
3. 實例方法:定義在類內部的普通方法,使用 this 訪問實例屬性。
4. 靜態方法:使用 static 關鍵字定義,不依賴于類的實例。
5. 實例屬性:在構造函數中初始化,或使用字段聲明語法(目前是 Stage 3 proposal)。
6. 繼承:使用 extends 關鍵字實現。
7. super 關鍵字:在子類的構造函數中調用父類的構造函數或方法。
8. getter 和 setter:使用 get 和 set定義屬性的訪問器。
9. 私有屬性和方法:使用 # 定義私有屬性和方法(目前是 Stage 3 proposal)。
1. 基本類定義和實例化
class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return `Point(${this.x}, ${this.y})`;}
}let point = new Point(10, 20);
console.log(point.toString()); // 輸出: Point(10, 20)
2. 靜態方法、屬性
class MathUtils {constructor() {console.log(MyClass.myStaticProp); // 42}static add(a, b) {return a + b;}static myStaticProp = 42;
}console.log(MathUtils.add(1, 2)); // 輸出: 3
3. 繼承和 super
class Rectangle {constructor(width, height) {this.width = width;this.height = height;}area() {return this.width * this.height;}
}class Square extends Rectangle {constructor(sideLength) {super(sideLength, sideLength);}
}let square = new Square(5);
console.log(square.area()); // 輸出: 25
4. getter 和 setter
class Rectangle {constructor(width, height) {this.width = width;this.height = height;}get area() {return this.width * this.height;}set width(newWidth) {if (newWidth > 0) {this.width = newWidth;} else {console.log("Width must be positive.");}}
}let rect = new Rectangle(4, 5);
console.log(rect.area); // 輸出: 20
rect.width = -10; // 輸出: Width must be positive.
class的注意點
(1)嚴格模式
類和模塊的內部,默認就是嚴格模式,所以不需要使用 use strict 指定運行模式。只要你的代碼寫在類或模塊之中,就只有嚴格模式可用。考慮到未來所有的代碼,其實都是運行在模塊之中,所以 ES6 實際上把整個語言升級到了嚴格模式。
(2)不存在提升
類不存在變量提升(hoist),這一點與 ES5 完全不同。
new Foo(); // ReferenceError
class Foo {}//不會報錯
//因為 Bar 繼承 Foo 的時候, Foo 已經有定義了。
//但是,如果存在 class 的提升,上面代碼就會報錯,
//因為 class 會被提升到代碼頭部,而 let 命令是不提升的,
//所以導致 Bar 繼承 Foo 的時候, Foo 還沒有定義。
{let Foo = class {};class Bar extends Foo {}
}
(3)name 屬性
由于本質上,ES6 的類只是 ES5 的構造函數的一層包裝,所以函數的許多特性都被Class繼承,包括 name 屬性。
class Point {}
Point.name // "Point"
//name 屬性總是返回緊跟在 class 關鍵字后面的類名。
(4)Generator 方法
如果某個方法之前加上星號( * ),就表示該方法是一個 Generator 函數。
class Foo {constructor(...args) {this.args = args;}* [Symbol.iterator]() {for (let arg of this.args) {yield arg;}}
}
for (let x of new Foo('hello', 'world')) {console.log(x);
}
// hello
// world//Foo 類的 Symbol.iterator 方法前有一個星號,表示該方法是一個 Generator 函數。
//Symbol.iterator 方法返回一個 Foo 類的默認遍歷器, for...of 循環會自動調用這個遍歷器。
(5)this 的指向
類的方法內部如果含有 this ,它默認指向類的實例。但是,必須非常小心,一旦單獨使用該方法,很可能報錯。
class Logger {printName(name = 'there') {this.print(`Hello ${name}`);}print(text) {console.log(text);}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
避免使用this,在構造方法中綁定 this:
class Logger {constructor() {this.printName = this.printName.bind(this);}// ...
}
避免使用this,使用箭頭函數:
class Obj {constructor() {this.getThis = () => this;}
}
const myObj = new Obj();
myObj.getThis() === myObj // true
避免使用this,使用 Proxy
function selfish (target) {const cache = new WeakMap();const handler = {get (target, key) {const value = Reflect.get(target, key);if (typeof value !== 'function') {return value;}if (!cache.has(value)) {cache.set(value, value.bind(target));}return cache.get(value);}};const proxy = new Proxy(target, handler);return proxy;
}
const logger = selfish(new Logger());