面向对象编程在 JavaScript 中的实践

0 阅读5分钟

引言

前端开发正随着应用复杂度增加而向更严谨的架构模式演进。JavaScript 作为一种多范式语言,其面向对象特性为构建可维护、可扩展的大型应用提供了强大基础。

一、JavaScript 中的对象模型演化

对象字面量:最基础的封装

对象字面量是 JavaScript 中最简单的对象创建方式,提供了基本的属性和方法封装。

const user = {
  name: "张三",
  role: "开发者",
  permissions: ["read", "write"],
  
  hasPermission(permission) {
    return this.permissions.includes(permission);
  },
  
  describe() {
    return `${this.name} 是一名 ${this.role}`;
  }
};

console.log(user.describe()); // "张三 是一名 开发者"

对象字面量适用于简单场景,但在需要创建多个相似对象时效率低下,且难以实现继承关系。

构造函数与原型链

构造函数为创建相似对象提供了模板,结合原型链可实现属性和方法的共享。

function User(name, role) {
  this.name = name;
  this.role = role;
  this.permissions = ["read"];
}

User.prototype.hasPermission = function(permission) {
  return this.permissions.includes(permission);
};

User.prototype.describe = function() {
  return `${this.name} 是一名 ${this.role}`;
};

// 继承实现
function Admin(name) {
  User.call(this, name, "管理员");
  this.permissions.push("write", "delete");
}

Admin.prototype = Object.create(User.prototype);
Admin.prototype.constructor = Admin;

const developer = new User("李四", "开发者");
const admin = new Admin("王五");

console.log(developer.describe()); // "李四 是一名 开发者"
console.log(admin.describe()); // "王五 是一名 管理员"
console.log(admin.hasPermission("delete")); // true

这种模式虽然强大,但原型继承的语法冗长且容易出错,特别是多层继承时。

ES6 Class:现代化的语法糖

ES6 引入的 class 语法提供了更清晰、更接近传统 OOP 的编程体验。

class User {
  constructor(name, role) {
    this.name = name;
    this.role = role;
    this.permissions = ["read"];
  }
  
  hasPermission(permission) {
    return this.permissions.includes(permission);
  }
  
  describe() {
    return `${this.name} 是一名 ${this.role}`;
  }
}

class Admin extends User {
  constructor(name) {
    super(name, "管理员");
    this.permissions.push("write", "delete");
  }
  
  // 方法重写示例
  describe() {
    return `${super.describe()} 拥有系统管理权限`;
  }
}

const admin = new Admin("赵六");
console.log(admin.describe()); // "赵六 是一名 管理员 拥有系统管理权限"

二、面向对象核心原则在 JavaScript 中的应用

封装:保护数据与实现

JavaScript 通过闭包和 Symbol 实现真正的私有属性:

// 使用闭包实现私有属性
function createUser(name) {
  let _password = "default"; // 私有变量
  
  return {
    name,
    setPassword(pwd) {
      if (pwd.length < 6) throw new Error("密码长度不足");
      _password = pwd;
    },
    authenticate(pwd) {
      return pwd === _password;
    }
  };
}

// 使用 ES2022 私有字段
class BankAccount {
  #balance = 0; // 私有字段
  
  constructor(owner) {
    this.owner = owner;
  }
  
  deposit(amount) {
    if (amount <= 0) throw new Error("存款金额必须为正数");
    this.#balance += amount;
    return this.#balance;
  }
  
  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount("张三");
account.deposit(100);
console.log(account.getBalance()); // 100
// console.log(account.#balance); // 语法错误:私有字段无法从外部访问

继承:代码复用与层次结构

JavaScript 继承可以通过类继承或组合实现:

// 类继承
class Component {
  constructor(selector) {
    this.element = document.querySelector(selector);
  }
  
  hide() { this.element.style.display = "none"; }
  show() { this.element.style.display = "block"; }
}

class Button extends Component {
  constructor(selector, text) {
    super(selector);
    this.element.textContent = text;
    this.element.addEventListener("click", this.onClick.bind(this));
  }
  
  onClick() {
    console.log("Button clicked");
  }
}

// 组合继承(更灵活)
function withDraggable(BaseComponent) {
  return class extends BaseComponent {
    constructor(...args) {
      super(...args);
      this.initDraggable();
    }
    
    initDraggable() {
      let isDragging = false;
      let offsetX, offsetY;
      
      this.element.addEventListener("mousedown", (e) => {
        isDragging = true;
        offsetX = e.clientX - this.element.getBoundingClientRect().left;
        offsetY = e.clientY - this.element.getBoundingClientRect().top;
      });
      
      document.addEventListener("mousemove", (e) => {
        if (!isDragging) return;
        this.element.style.left = `${e.clientX - offsetX}px`;
        this.element.style.top = `${e.clientY - offsetY}px`;
      });
      
      document.addEventListener("mouseup", () => {
        isDragging = false;
      });
    }
  };
}

const DraggableButton = withDraggable(Button);

多态性:接口一致性与行为差异

多态允许不同类通过相同接口提供不同实现:

class Shape {
  area() {
    throw new Error("子类必须实现 area 方法");
  }
  
  toString() {
    return `${this.constructor.name}: 面积 = ${this.area()}`;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }
  
  area() {
    return Math.PI * this.radius ** 2;
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }
  
  area() {
    return this.width * this.height;
  }
}

// 多态性体现:同一个函数处理不同类型
function printArea(shape) {
  if (!(shape instanceof Shape)) 
    throw new Error("参数必须是 Shape 类型");
  console.log(shape.toString());
}

printArea(new Circle(5)); // "Circle: 面积 = 78.53981633974483"
printArea(new Rectangle(4, 5)); // "Rectangle: 面积 = 20"

三、设计模式在 JavaScript 项目中的应用

单例模式:全局状态管理

// 现代 JavaScript 单例实现
class UserStore {
  static instance = null;
  
  constructor() {
    if (UserStore.instance) {
      return UserStore.instance;
    }
    
    this.user = null;
    this.listeners = [];
    UserStore.instance = this;
  }
  
  setUser(userData) {
    this.user = userData;
    this.notifyListeners();
  }
  
  getUser() {
    return this.user;
  }
  
  subscribe(listener) {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }
  
  notifyListeners() {
    this.listeners.forEach(listener => listener(this.user));
  }
}

const userStore = new UserStore();
export default userStore;

工厂模式:对象创建抽象

// 组件工厂示例
class ComponentFactory {
  static createComponent(type, props) {
    switch (type) {
      case 'button':
        return new Button(props);
      case 'input':
        return new Input(props);
      case 'modal':
        return new Modal(props);
      default:
        throw new Error(`不支持创建类型: ${type}`);
    }
  }
}

// 使用工厂创建组件
const loginButton = ComponentFactory.createComponent('button', { 
  text: '登录', 
  onClick: () => console.log('登录点击')
});

观察者模式:事件驱动架构

class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
    
    return () => this.off(event, listener);
  }
  
  off(event, listener) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter(l => l !== listener);
  }
  
  emit(event, ...args) {
    if (!this.events[event]) return;
    this.events[event].forEach(listener => listener(...args));
  }
  
  once(event, listener) {
    const onceListener = (...args) => {
      listener(...args);
      this.off(event, onceListener);
    };
    this.on(event, onceListener);
  }
}

// 在应用中使用事件系统
class TodoApplication extends EventEmitter {
  constructor() {
    super();
    this.todos = [];
  }
  
  addTodo(text) {
    const todo = {
      id: Date.now(),
      text,
      completed: false
    };
    this.todos.push(todo);
    this.emit('todo:added', todo);
  }
  
  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
      this.emit('todo:toggled', todo);
    }
  }
}

const app = new TodoApplication();
app.on('todo:added', todo => {
  console.log(`新任务添加: ${todo.text}`);
  // 更新 UI
});

四、依赖管理与架构设计

依赖注入实现可测试代码

// 依赖注入示例
class UserService {
  constructor(apiClient) {
    this.apiClient = apiClient; // 注入依赖
  }
  
  async getUsers() {
    return this.apiClient.get('/users');
  }
  
  async getUserById(id) {
    return this.apiClient.get(`/users/${id}`);
  }
}

// 可以注入不同实现以方便测试
const productionApiClient = {
  get: async (url) => fetch(url).then(r => r.json())
};

const mockApiClient = {
  get: async (url) => {
    if (url === '/users') return Promise.resolve([
      { id: 1, name: '张三' },
      { id: 2, name: '李四' }
    ]);
    
    const match = url.match(/\/users\/(\d+)/);
    if (match) {
      const id = parseInt(match[1]);
      return Promise.resolve({ id, name: id === 1 ? '张三' : '李四' });
    }
    
    return Promise.reject(new Error('未找到资源'));
  }
};

// 使用真实依赖
const userService = new UserService(productionApiClient);

// 测试环境使用 mock
const testUserService = new UserService(mockApiClient);

模块化与可扩展架构

// 模块接口定义
class ModuleInterface {
  init() { throw new Error("需实现 init 方法"); }
  destroy() { throw new Error("需实现 destroy 方法"); }
}

// 具体模块实现
class AuthModule extends ModuleInterface {
  constructor(apiClient, eventBus) {
    super();
    this.apiClient = apiClient;
    this.eventBus = eventBus;
    this.currentUser = null;
  }
  
  init() {
    this.checkAuthStatus();
    this.setupListeners();
  }
  
  destroy() {
    // 清理资源
    this.eventBus.off('logout', this.handleLogout);
  }
  
  async checkAuthStatus() {
    try {
      const user = await this.apiClient.get('/auth/me');
      this.setCurrentUser(user);
    } catch (err) {
      this.setCurrentUser(null);
    }
  }
  
  setupListeners() {
    this.handleLogout = () => this.logout();
    this.eventBus.on('logout', this.handleLogout);
  }
  
  async login(credentials) {
    const user = await this.apiClient.post('/auth/login', credentials);
    this.setCurrentUser(user);
    return user;
  }
  
  async logout() {
    await this.apiClient.post('/auth/logout');
    this.setCurrentUser(null);
  }
  
  setCurrentUser(user) {
    this.currentUser = user;
    this.eventBus.emit('auth:changed', user);
  }
}

// 应用程序核心
class Application {
  constructor() {
    this.modules = new Map();
    this.eventBus = new EventEmitter();
    this.apiClient = {
      get: async (url) => fetch(url).then(r => r.json()),
      post: async (url, data) => fetch(url, {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(data)
      }).then(r => r.json())
    };
  }
  
  registerModule(name, ModuleClass) {
    const module = new ModuleClass(this.apiClient, this.eventBus);
    this.modules.set(name, module);
  }
  
  init() {
    for (const module of this.modules.values()) {
      module.init();
    }
  }
  
  getModule(name) {
    return this.modules.get(name);
  }
}

// 使用示例
const app = new Application();
app.registerModule('auth', AuthModule);
app.init();

// 在任何地方使用认证模块
const authModule = app.getModule('auth');
authModule.login({ username: 'user', password: 'pass' });

五、性能考量与最佳实践

原型链与内存优化

通过原型共享方法,可大幅减少内存占用:

// 不优化:每个实例都有自己的方法副本
function BadUser(name) {
  this.name = name;
  this.sayHi = function() { // 每个实例都创建一个新函数
    return `你好,我是 ${this.name}`;
  };
}

// 优化:所有实例共享同一方法
function GoodUser(name) {
  this.name = name;
}
GoodUser.prototype.sayHi = function() {
  return `你好,我是 ${this.name}`;
};

// 性能测试
function testMemory(Constructor, count) {
  const instances = [];
  for (let i = 0; i < count; i++) {
    instances.push(new Constructor(`用户${i}`));
  }
  return instances;
}

// 创建 10000 个实例进行对比
const badUsers = testMemory(BadUser, 10000); // 内存占用更高
const goodUsers = testMemory(GoodUser, 10000); // 内存占用更低

什么时候不应该使用 OOP

对象系统也有其局限性:

// 适合函数式处理的例子
const numbers = [1, 2, 3, 4, 5];

// 函数式写法 - 简洁明了
const doubled = numbers.map(n => n * 2);
const sum = numbers.reduce((acc, n) => acc + n, 0);

// OOP 写法 - 过度设计
class NumberCollection {
  constructor(numbers) {
    this.numbers = numbers;
  }
  
  map(transform) {
    return new NumberCollection(this.numbers.map(transform));
  }
  
  sum() {
    return this.numbers.reduce((acc, n) => acc + n, 0);
  }
}

const collection = new NumberCollection(numbers);
const doubledOOP = collection.map(n => n * 2).numbers;
const sumOOP = collection.sum();

总结

面向对象编程为 JavaScript 提供了强大的结构化工具,从简单的对象字面量到现代化的 ES6 类语法。我们可以根据项目需求选择合适的抽象级别。在大型应用架构中,恰当运用封装、继承和多态原则,结合设计模式实践,才可以构建出既灵活又健壮的系统。

JavaScript 的面向对象编程需要结合语言的动态特性与函数式编程范式,形成一种平衡的开发方法。掌握这些技巧才能在实际项目中构建出经得起时间考验的代码库。

参考资源

官方文档

在线教程

社区资源


如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻