Skip to content

Class 基础

ES6 的 Class 是基于原型继承的语法糖,让面向对象编程更加直观清晰。

基本语法#

定义类#

class Person {
// 构造函数
constructor(name, age) {
this.name = name
this.age = age
}
// 实例方法
sayHello() {
console.log(`你好,我是 ${this.name}`)
}
// getter
get info() {
return `${this.name}, ${this.age}`
}
// setter
set info(value) {
;[this.name, this.age] = value.split(', ')
}
}
const person = new Person('张三', 25)
person.sayHello() // 你好,我是 张三
console.log(person.info) // 张三, 25岁

ES5 对比#

// ES6 Class
class Person {
constructor(name) {
this.name = name
}
sayHello() {
console.log(`Hello, ${this.name}`)
}
}
// 等价的 ES5 写法
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function () {
console.log('Hello, ' + this.name)
}

类的本质#

class Person {}
typeof Person // 'function'
Person === Person.prototype.constructor // true
// 类的方法定义在原型上
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
return `(${this.x}, ${this.y})`
}
}
Point.prototype.toString // [Function: toString]

constructor 方法#

基本规则#

class Person {
constructor(name) {
this.name = name
// 默认返回 this
}
}
// 不写 constructor 会有默认的空构造函数
class Empty {
// constructor() {}
}

返回值#

// 返回其他对象会改变 new 的结果
class Foo {
constructor() {
return { custom: true }
}
}
const foo = new Foo()
foo instanceof Foo // false
foo.custom // true

必须使用 new#

class Person {
constructor(name) {
this.name = name
}
}
// Person('张三'); // TypeError: Class constructor cannot be invoked without 'new'
new Person('张三') // OK

实例属性#

在 constructor 中定义#

class Person {
constructor(name) {
this.name = name
this.friends = []
}
}

类字段声明(ES2022)#

class Person {
// 公有字段
name = '默认名称'
age = 0
friends = []
constructor(name, age) {
if (name) this.name = name
if (age) this.age = age
}
}
const person = new Person()
console.log(person.name) // '默认名称'

私有字段(ES2022)#

class BankAccount {
#balance = 0 // 私有字段
constructor(initial) {
this.#balance = initial
}
deposit(amount) {
this.#balance += amount
}
withdraw(amount) {
if (amount > this.#balance) {
throw new Error('余额不足')
}
this.#balance -= amount
}
get balance() {
return this.#balance
}
}
const account = new BankAccount(1000)
account.deposit(500)
console.log(account.balance) // 1500
// console.log(account.#balance); // SyntaxError: 私有字段

实例方法#

方法简写#

class Calculator {
add(a, b) {
return a + b
}
subtract(a, b) {
return a - b
}
}
// 方法在原型上,所有实例共享
const calc1 = new Calculator()
const calc2 = new Calculator()
calc1.add === calc2.add // true

私有方法(ES2022)#

class Person {
#privateMethod() {
return '私有方法'
}
publicMethod() {
return this.#privateMethod()
}
}
const person = new Person()
person.publicMethod() // '私有方法'
// person.#privateMethod(); // SyntaxError

生成器方法#

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]

getter 和 setter#

基本用法#

class Circle {
constructor(radius) {
this._radius = radius
}
get radius() {
return this._radius
}
set radius(value) {
if (value < 0) {
throw new Error('半径不能为负数')
}
this._radius = value
}
get area() {
return Math.PI * this._radius ** 2
}
get circumference() {
return 2 * Math.PI * this._radius
}
}
const circle = new Circle(5)
console.log(circle.area) // 78.54...
circle.radius = 10
console.log(circle.area) // 314.16...

计算属性#

class Rectangle {
constructor(width, height) {
this.width = width
this.height = height
}
get area() {
return this.width * this.height
}
get perimeter() {
return 2 * (this.width + this.height)
}
get diagonal() {
return Math.sqrt(this.width ** 2 + this.height ** 2)
}
}

静态成员#

静态方法#

class MathUtils {
static add(a, b) {
return a + b
}
static multiply(a, b) {
return a * b
}
static PI = 3.14159
}
MathUtils.add(1, 2) // 3
MathUtils.PI // 3.14159
// 静态方法不能通过实例调用
const utils = new MathUtils()
// utils.add(1, 2); // TypeError

静态字段(ES2022)#

class Config {
static version = '1.0.0'
static #secret = 'private' // 私有静态字段
static getSecret() {
return this.#secret
}
}
Config.version // '1.0.0'
Config.getSecret() // 'private'

工厂模式#

class User {
constructor(name, role) {
this.name = name
this.role = role
}
static createAdmin(name) {
return new User(name, 'admin')
}
static createGuest() {
return new User('访客', 'guest')
}
}
const admin = User.createAdmin('张三')
const guest = User.createGuest()

单例模式#

class Database {
static #instance = null
constructor() {
if (Database.#instance) {
return Database.#instance
}
Database.#instance = this
}
static getInstance() {
if (!Database.#instance) {
Database.#instance = new Database()
}
return Database.#instance
}
query(sql) {
console.log('执行查询:', sql)
}
}
const db1 = Database.getInstance()
const db2 = Database.getInstance()
db1 === db2 // true

静态初始化块(ES2022)#

class Config {
static settings
static #privateData
static {
// 复杂的静态初始化逻辑
try {
const data = loadConfig()
this.settings = data.settings
this.#privateData = data.secret
} catch (error) {
this.settings = getDefaultSettings()
this.#privateData = null
}
}
static getPrivateData() {
return this.#privateData
}
}

类表达式#

匿名类表达式#

const Person = class {
constructor(name) {
this.name = name
}
sayHello() {
console.log(`Hello, ${this.name}`)
}
}
new Person('张三').sayHello()

命名类表达式#

const Person = class PersonClass {
constructor(name) {
this.name = name
}
static create(name) {
return new PersonClass(name) // 内部可以使用
}
}
// PersonClass 只在类内部可用
// new PersonClass('张三'); // ReferenceError

立即执行的类#

const person = new (class {
constructor(name) {
this.name = name
}
sayHello() {
return `Hello, ${this.name}`
}
})('张三')
person.sayHello() // 'Hello, 张三'

this 指向#

方法中的 this#

class Counter {
count = 0
increment() {
this.count++
}
}
const counter = new Counter()
// 直接调用没问题
counter.increment()
console.log(counter.count) // 1
// 但作为回调时 this 会丢失
const { increment } = counter
// increment(); // TypeError: Cannot read property 'count' of undefined

解决方案#

// 方案1:箭头函数字段
class Counter {
count = 0
increment = () => {
this.count++
}
}
// 方案2:bind
class Counter {
constructor() {
this.increment = this.increment.bind(this)
}
count = 0
increment() {
this.count++
}
}
// 方案3:调用时绑定
const counter = new Counter()
button.addEventListener('click', () => counter.increment())

in 操作符检查私有字段#

class Person {
#name
constructor(name) {
this.#name = name
}
static isPerson(obj) {
return #name in obj
}
}
Person.isPerson(new Person('张三')) // true
Person.isPerson({}) // false

常见模式#

链式调用#

class QueryBuilder {
#query = ''
select(fields) {
this.#query += `SELECT ${fields} `
return this
}
from(table) {
this.#query += `FROM ${table} `
return this
}
where(condition) {
this.#query += `WHERE ${condition} `
return this
}
build() {
return this.#query.trim()
}
}
const query = new QueryBuilder()
.select('*')
.from('users')
.where('age > 18')
.build()
// 'SELECT * FROM users WHERE age > 18'

混入(Mixin)#

const Serializable = (Base) =>
class extends Base {
toJSON() {
return JSON.stringify({ ...this })
}
static fromJSON(json) {
return Object.assign(new this(), JSON.parse(json))
}
}
const Comparable = (Base) =>
class extends Base {
equals(other) {
return JSON.stringify(this) === JSON.stringify(other)
}
}
class User extends Serializable(Comparable(Object)) {
constructor(name, age) {
super()
this.name = name
this.age = age
}
}
const user = new User('张三', 25)
console.log(user.toJSON()) // '{"name":"张三","age":25}'

小结#

特性说明
class类声明
constructor构造函数
实例方法定义在原型上的方法
静态成员static 关键字定义
私有成员# 前缀(ES2022)
getter/setter访问器属性

Class 让 JavaScript 的面向对象编程更加清晰,但本质上仍是基于原型的继承。