1. ES6面向对象编程的核心变革
在JavaScript发展历程中,ES6(ECMAScript 2015)对面向对象编程范式进行了革命性升级。传统ES5通过原型链(prototype chain)和构造函数(constructor function)实现面向对象特性,而ES6引入了更符合主流编程语言的class语法糖,让面向对象编程变得更加直观和易于理解。
重要提示:class语法本质仍是基于原型的继承,但提供了更清晰的抽象层。理解这一点对掌握JavaScript面向对象编程至关重要。
1.1 class声明与构造函数
ES6中通过class关键字定义类,constructor方法作为类的构造函数:
javascript复制class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}`;
}
}
const john = new Person('John', 30);
console.log(john.greet()); // "Hello, my name is John"
与ES5相比,这种写法更接近传统面向对象语言(如Java、C#)的风格。但要注意几个关键区别:
- class声明不会提升(hoisting),必须先声明后使用
- 类方法默认不可枚举(non-enumerable)
- 调用类必须使用new关键字,否则会抛出TypeError
1.2 继承与super关键字
ES6通过extends关键字实现继承,super用于调用父类构造函数或方法:
javascript复制class Employee extends Person {
constructor(name, age, position) {
super(name, age); // 调用父类构造函数
this.position = position;
}
introduce() {
return `${super.greet()} and I'm a ${this.position}`;
}
}
const dev = new Employee('Alice', 28, 'Developer');
console.log(dev.introduce());
// "Hello, my name is Alice and I'm a Developer"
这种继承机制比ES5的原型继承更清晰,避免了手动设置prototype和__proto__的复杂性。super的使用需要注意:
- 在constructor中必须先调用super()才能使用this
- 在方法中super.method()会正确绑定this值
- 静态方法也可以通过super调用父类的静态方法
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 类的高级特性与设计模式
2.1 静态方法与属性
静态成员属于类本身而非实例:
javascript复制class MathUtils {
static PI = 3.14159;
static circleArea(radius) {
return this.PI * radius ** 2;
}
}
console.log(MathUtils.circleArea(5)); // 78.53975
静态方法常用于工具函数或工厂模式。在ES2022之前,静态属性需要通过类外部赋值实现,现在可以直接在类内部声明。
2.2 getter与setter
通过get和set关键字定义访问器属性:
javascript复制class Temperature {
constructor(celsius) {
this._celsius = celsius;
}
get fahrenheit() {
return this._celsius * 9/5 + 32;
}
set fahrenheit(value) {
this._celsius = (value - 32) * 5/9;
}
}
const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 100;
console.log(temp._celsius); // 37.777...
这种封装方式比直接暴露属性更安全,可以在访问时添加验证逻辑或计算派生值。
2.3 私有字段与方法
ES2022正式加入了私有成员语法(#前缀):
javascript复制class BankAccount {
#balance = 0; // 私有字段
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
get balance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(500);
console.log(account.balance); // 500
console.log(account.#balance); // SyntaxError
私有成员解决了长期以来JavaScript缺乏真正封装的问题,是大型项目开发的重要特性。
3. 基于类的设计模式实践
3.1 工厂模式
利用静态方法创建不同类别的对象:
javascript复制class Vehicle {
constructor(type, wheels) {
this.type = type;
this.wheels = wheels;
}
static createCar() {
return new Vehicle('car', 4);
}
static createBike() {
return new Vehicle('bike', 2);
}
}
const myCar = Vehicle.createCar();
console.log(myCar.type); // "car"
3.2 单例模式
通过静态属性和私有构造函数确保只有一个实例:
javascript复制class AppConfig {
static #instance;
#settings = {};
constructor() {
if (AppConfig.#instance) {
return AppConfig.#instance;
}
this.#settings = { theme: 'dark', apiUrl: '...' };
AppConfig.#instance = this;
}
static getInstance() {
if (!this.#instance) {
this.#instance = new AppConfig();
}
return this.#instance;
}
}
const config1 = AppConfig.getInstance();
const config2 = AppConfig.getInstance();
console.log(config1 === config2); // true
3.3 观察者模式
利用类实现发布-订阅机制:
javascript复制class EventEmitter {
#listeners = {};
on(event, callback) {
if (!this.#listeners[event]) {
this.#listeners[event] = [];
}
this.#listeners[event].push(callback);
}
emit(event, ...args) {
(this.#listeners[event] || []).forEach(cb => cb(...args));
}
}
class Store extends EventEmitter {
#state = {};
updateState(newState) {
this.#state = { ...this.#state, ...newState };
this.emit('stateChange', this.#state);
}
}
const store = new Store();
store.on('stateChange', state => console.log('State updated:', state));
store.updateState({ user: 'Alice' }); // 触发事件
4. 类与原型的关系揭秘
4.1 class的底层实现
虽然class语法更直观,但底层仍然是基于原型的继承。以下代码展示了等效的ES5实现:
javascript复制// ES6 class
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
// 等效ES5实现
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
};
关键区别在于:
- class方法不可枚举
- class构造函数必须用new调用
- class继承会自动设置原型链
4.2 原型链可视化
理解原型链对调试和高级用法至关重要。对于以下类结构:
javascript复制class A {}
class B extends A {}
const b = new B();
原型链关系为:
code复制b -> B.prototype -> A.prototype -> Object.prototype -> null
可以通过以下方法验证:
javascript复制console.log(b.__proto__ === B.prototype); // true
console.log(B.prototype.__proto__ === A.prototype); // true
console.log(A.prototype.__proto__ === Object.prototype); // true
4.3 方法查找机制
当调用实例方法时,JavaScript引擎会:
- 检查实例自身属性
- 沿原型链向上查找
- 找到第一个匹配的属性后停止
- 如果到Object.prototype仍未找到则返回undefined
这种机制解释了为什么重写方法会影响所有实例:
javascript复制class Base {
log() { console.log('Base'); }
}
class Derived extends Base {
// 重写方法
log() { console.log('Derived'); }
}
const d = new Derived();
d.log(); // "Derived"
// 修改原型方法会影响所有实例
Base.prototype.log = () => console.log('Modified');
d.log(); // "Modified"
5. 实战中的注意事项与性能考量
5.1 内存使用优化
每个类方法都会在原型上创建一次,被所有实例共享。但箭头函数作为类字段会在每个实例上创建:
javascript复制class Logger {
log1() {} // 在原型上
log2 = () => {}; // 在每个实例上
}
const a = new Logger();
const b = new Logger();
console.log(a.log1 === b.log1); // true
console.log(a.log2 === b.log2); // false
对于大量实例的类,应优先使用原型方法减少内存占用。
5.2 this绑定问题
类方法中的this值取决于调用方式。常见陷阱:
javascript复制class Button {
constructor() {
this.text = 'Click me';
}
handleClick() {
console.log(this.text);
}
}
const btn = new Button();
document.addEventListener('click', btn.handleClick); // undefined
解决方案:
- 在构造函数中绑定:
javascript复制this.handleClick = this.handleClick.bind(this);
- 使用箭头函数类字段:
javascript复制handleClick = () => {
console.log(this.text);
}
5.3 类与模块系统
现代JavaScript项目通常使用模块系统组织类:
javascript复制// shapes.js
export class Circle {
constructor(radius) {
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
// app.js
import { Circle } from './shapes.js';
const c = new Circle(5);
console.log(c.area());
这种组织方式使代码更易维护,也支持tree-shaking优化。
5.4 性能考量
虽然class语法更清晰,但在某些性能关键场景需要注意:
- 频繁创建销毁的小对象可能影响GC性能
- 深度继承链会影响方法查找速度
- 动态修改原型会影响隐藏类优化
对于高性能需求,有时简单的工厂函数可能更高效:
javascript复制function createVector(x, y) {
return {
x, y,
length() {
return Math.sqrt(this.x**2 + this.y**2);
}
};
}
