简介
生成实例的传统方法:通过构造函数;
ES6 引入了class,作为”对象的模板”;通过class定义类。
基本上,ES6 的class可以看作只是一个语法糖。
class 声明
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
typeof Point; // "function"
Point === Point.prototype.constructor; // true
Point.name; // "Point"
// 必须通过 new 调用,否则报错(与普通构造函数的区别)
const p = new Point(1, 2);
class 表达式
// 命名表达式:Me 只在 class 内部可用
const MyClass = class Me {
getClassName() { return Me.name; }
};
new MyClass().getClassName(); // "Me"
// Me.name // ReferenceError
// 匿名表达式(更常用)
const MyClass2 = class { /* ... */ };
// 立即执行
const obj = new (class {
constructor(name) { this.name = name; }
})('test');
obj.name; // "test"
class 不存在变量提升
new Foo(); // ReferenceError
class Foo {}
// 原因与继承有关,必须保证子类在父类之后定义
{
let Foo = class {};
class Bar extends Foo {}
}
constructor
构造方法,创建实例时自动调用。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// 不写 constructor,默认生成空的 constructor() {}
// 返回其他对象的情况
class Odd {
constructor() { return Object.create(null); }
}
new Odd() instanceof Odd; // false
实例方法
方法定义在 prototype 上,所有实例共享。
class Calculator {
constructor(value = 0) { this.value = value; }
add(n) { this.value += n; return this; }
subtract(n) { this.value -= n; return this; }
getResult() { return this.value; }
}
const calc = new Calculator(10);
calc.add(5).subtract(3).getResult(); // 12
// 方法在 prototype 上
calc.hasOwnProperty('add'); // false
calc.__proto__.hasOwnProperty('add'); // true
// 类内部定义的方法不可枚举(与 ES5 不同)
Object.keys(Calculator.prototype); // []
Object.getOwnPropertyNames(Calculator.prototype);
// ["constructor", "add", "subtract", "getResult"]
一次添加多个方法:Object.assign(Point.prototype, { method1() {}, method2() {} })
静态方法
static 定义的方法属于类本身,不被实例继承。
class MathUtil {
static add(a, b) { return a + b; }
static isPositive(n) { return n > 0; }
}
MathUtil.add(1, 2); // 3
// new MathUtil().add(1, 2); // TypeError
// 静态方法中 this 指向类本身
class Foo {
static create() { return new this(); }
static bar() { return this.baz(); }
static baz() { return 'baz'; }
}
实例属性和静态属性
class Counter {
// 实例属性(类体中直接声明)
count = 0;
// 静态属性
static total = 0;
constructor() { Counter.total++; }
increment() { this.count++; }
}
const c1 = new Counter();
const c2 = new Counter();
Counter.total; // 2
c1.count; // 0(各实例独立)
getter 和 setter
使用 get/set 拦截属性的存取行为。
class Temperature {
constructor(celsius) { this._celsius = celsius; }
get fahrenheit() { return this._celsius * 1.8 + 32; }
set fahrenheit(value) { this._celsius = (value - 32) / 1.8; }
get celsius() { return this._celsius; }
set celsius(value) {
if (value < -273.15) throw new Error('Below absolute zero');
this._celsius = value;
}
}
const temp = new Temperature(100);
temp.fahrenheit; // 212
temp.fahrenheit = 32;
temp.celsius; // 0
getter/setter 定义在属性的 Descriptor 对象上:
var desc = Object.getOwnPropertyDescriptor(Temperature.prototype, 'fahrenheit');
'get' in desc; // true
'set' in desc; // true
私有字段和方法(# 语法)
ES2022 正式支持 # 前缀定义私有成员,只能在类内部访问。
class BankAccount {
#balance = 0;
constructor(owner, balance) {
this.owner = owner;
this.#balance = balance;
}
#validate(amount) {
if (amount <= 0) throw new Error('Amount must be positive');
}
deposit(amount) { this.#validate(amount); this.#balance += amount; }
withdraw(amount) {
this.#validate(amount);
if (amount > this.#balance) throw new Error('Insufficient funds');
this.#balance -= amount;
}
get balance() { return this.#balance; }
}
const account = new BankAccount('Alice', 1000);
account.deposit(500);
account.balance; // 1500
// account.#balance; // SyntaxError
# 语法之前的私有化方案:
// 1. 命名约定(仅是约定,外部仍可访问)
class Foo { _private() {} }
// 2. Symbol 唯一性
const _method = Symbol('method');
class Baz { [_method]() { return 'private'; } }
this 指向问题
类方法中 this 默认指向实例,但单独提取方法时会丢失。
class Logger {
constructor(prefix) { this.prefix = prefix; }
log(msg) { console.log(`[${this.prefix}] ${msg}`); }
}
const { log } = new Logger('App');
// log('test'); // TypeError: Cannot read property 'prefix' of undefined
// 解决方案:constructor 中 bind,或使用箭头函数属性
// this.log = this.log.bind(this);
// log = (msg) => { console.log(`[${this.prefix}] ${msg}`); }
new.target
返回 new 命令作用于的构造函数,可实现抽象类。
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('Shape 不能直接实例化');
}
}
}
class Circle extends Shape {
constructor(r) { super(); this.radius = r; }
}
// new Shape(); // Error
new Circle(5); // OK
与传统构造函数的对比
// 传统构造函数
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { return this.name + ' speaks'; };
Animal.staticMethod = function() { return 'static'; };
// ES6 class
class Animal2 {
constructor(name) { this.name = name; }
speak() { return this.name + ' speaks'; }
static staticMethod() { return 'static'; }
}
| 特性 | 构造函数 | class |
|---|---|---|
| 变量提升 | 有 | 无 |
| 必须 new 调用 | 否 | 是 |
| 方法可枚举 | 是 | 否 |
| 严格模式 | 需手动声明 | 自动 |
| 私有字段 | 无 | # 语法 |
Generator 方法
方法前加星号使其成为 Generator 函数。
class Range {
constructor(start, end) { this.start = start; this.end = end; }
* [Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) yield i;
}
}
[...new Range(1, 5)]; // [1, 2, 3, 4, 5]