如何用javascript实现面向对象编程【教程】

JavaScript的class只是语法糖,面向对象取决于是否用封装、继承、多态组织代码;实例属性须在constructor中初始化,方法挂载原型,私有字段用#,多态依赖鸭子类型而非类型系统。

JavaScript 本身没有 class 关键字的年代就支持面向对象,现在有了 class 也只是语法糖——真正决定是否“面向对象”的,是你是否在用封装、继承、多态的思路组织代码,而不是有没有 newextends

class 声明构造逻辑,但别把它当 Java 类用

class 在 JavaScript 中不创建类型系统,它只是 function 的包装。实例的原型链依然指向 MyClass.prototype,所有方法都挂载在原型上,而非每个实例独有一份。

实操建议:

  • 把实例属性写在 constructor 内(如 this.name = name),否则容易误以为是“类字段”而漏初始化
  • 避免在 class 中直接写 function 表达式作为方法(如 method = () => {}),这会破坏原型链,且无法被子类 super.method() 正确调用
  • 静态方法用 static,但它不能访问 this 实例,只适合工具逻辑(如 User.createAdmin()
class User {
  constructor(name) {
    this.name = name; // ✅ 实例属性必须在这里赋值
  }
  greet() {
    return `Hello, ${this.name}`;
  }
  static isUser(obj) {
    return obj instanceof User;
  }
}

继承要小心 super() 调用时机和 this 绑定

子类构造函数中,必须在使用 this 前调用 super();否则会报 ReferenceError: Must call super constructor in derived class before accessing 'this'

常见错误现象:

  • 忘记 super() → 直接报错退出
  • super() 放在 this 之后 → 同样报错
  • 用箭头函数重写父类方法 → this 指向外层作用域,不是当前实例

实操建议:

  • 子类 constructor 第一行就写 super(...args)
  • 想复用父类逻辑,用 super.methodName(),不要手动绑定 Parent.prototype.methodName.call(this)
  • 如果需要修改父类方法行为,优先用 sup

    er
    调用再处理,而不是完全覆盖

“私有”不是靠命名约定,而是靠 # 字段或闭包

下划线前缀(如 _id)只是提示,无法阻止外部读写。# 开头的字段才是真私有:语法强制不可访问,且不会出现在 for...inObject.keys()JSON.stringify() 中。

实操建议:

  • # 字段存敏感状态(如 #token#balance),它比 Symbol 或闭包更简洁、可读性更高
  • 私有字段必须在 constructor 或字段声明处初始化(class A { #x = 0; }),不能动态添加
  • 若需兼容旧环境(如 IE 或 Node.js this 的方法
class BankAccount {
  #balance = 0;
  constructor(initial) {
    this.#balance = initial;
  }
  deposit(amount) {
    this.#balance += amount; // ✅ 可访问
  }
  getBalance() {
    return this.#balance; // ✅ 可返回
  }
}
// new BankAccount(100).#balance // ❌ SyntaxError

多态靠运行时判定,不是类型声明

JavaScript 没有接口或抽象类语法,所谓“多态”就是:同一段代码(如 draw(shape))能接受不同结构的对象,并根据其实际方法存在与否或返回值做分支处理。

实操建议:

  • 优先用“鸭子类型”:检查对象是否有某方法(if ('render' in obj && typeof obj.render === 'function')),而不是 instanceof
  • 避免深度依赖继承链实现多态;组合(class Button { constructor(renderer) { this.renderer = renderer; } )往往比继承更灵活
  • 用 TypeScript 的 interface 或 JSDoc 的 @typedef 做契约提示,但别当成运行时保障

真正难的不是写出 class,而是判断什么时候不该用 class——比如配置对象、纯数据容器、一次性的策略函数,用字面量或函数更轻量。