面向对象编程

JavaScript 中的面向对象编程概念与实现

问题

什么是面向对象编程?JavaScript 如何实现面向对象?

解答

面向对象编程(OOP)是一种以对象为中心的编程范式,通过封装、继承、多态来组织代码。

三大特性

封装:将数据和操作数据的方法绑定在一起,隐藏内部实现。

class User {
  #password; // 私有属性

  constructor(name, password) {
    this.name = name;
    this.#password = password;
  }

  // 公开方法访问私有数据
  checkPassword(input) {
    return this.#password === input;
  }
}

const user = new User("张三", "123456");
console.log(user.name); // "张三"
console.log(user.checkPassword("123456")); // true
// console.log(user.#password); // 报错,无法访问私有属性

继承:子类继承父类的属性和方法。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} 发出声音`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类构造函数
    this.breed = breed;
  }

  // 重写父类方法
  speak() {
    console.log(`${this.name} 汪汪叫`);
  }

  fetch() {
    console.log(`${this.name} 捡球`);
  }
}

const dog = new Dog("旺财", "柴犬");
dog.speak(); // "旺财 汪汪叫"
dog.fetch(); // "旺财 捡球"

多态:同一方法在不同对象上有不同表现。

class Shape {
  getArea() {
    return 0;
  }
}

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

  getArea() {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  getArea() {
    return Math.PI * this.radius ** 2;
  }
}

// 同一方法,不同行为
const shapes = [new Rectangle(10, 5), new Circle(7)];
shapes.forEach((shape) => {
  console.log(shape.getArea());
});
// 50
// 153.93...

构造函数方式(ES5)

function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 方法定义在原型上,所有实例共享
Person.prototype.sayHello = function () {
  console.log(`我是 ${this.name},今年 ${this.age} 岁`);
};

const p1 = new Person("李四", 25);
const p2 = new Person("王五", 30);

p1.sayHello(); // "我是 李四,今年 25 岁"
console.log(p1.sayHello === p2.sayHello); // true,共享同一方法

原型链继承

function Parent(name) {
  this.name = name;
}

Parent.prototype.getName = function () {
  return this.name;
};

function Child(name, age) {
  Parent.call(this, name); // 继承属性
  this.age = age;
}

// 继承方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.getAge = function () {
  return this.age;
};

const child = new Child("小明", 10);
console.log(child.getName()); // "小明"
console.log(child.getAge()); // 10

关键点

  • 封装:使用私有属性(#)隐藏内部实现,通过公开方法暴露接口
  • 继承:ES6 用 extendssuper,ES5 用 Object.createcall
  • 多态:子类重写父类方法,实现不同行为
  • 原型链:JavaScript 通过原型链实现继承,方法定义在 prototype 上可被所有实例共享
  • class 是语法糖:ES6 的 class 本质上还是基于原型的继承