Skip to content

面向对象编程(Object-Oriented Programming,OOP)是一种重要的技术,有助于编写清晰可靠的可以重复使用的代码。

1. 面向对象编程

1.1 概述

面向对象编程(Object-Oriented Programming, OOP) 是一种编程范式,旨在通过“对象”来组织代码逻辑。与 面向过程编程(Procedural Programming) 关注执行步骤不同,OOP 将数据(属性)和操作数据的方法(行为)封装在对象中,通过对象之间的交互构建系统。其核心目标是提高代码的重用性、可维护性和扩展性。

1.2 核心概念:类与实例

1.2.1 类 (Class)

类是对一组具有相同属性和行为的事物的抽象定义。它规定了对象的结构和功能,属于一种逻辑上的定义。

1.2.2 实例 (Instance) / 对象 (Object)

实例是根据类创建的具体实体。实例占有实际的内存空间,具备类所定义的具体属性值和可执行的方法。

// 定义类
class Person {
    constructor(name, age) {
        this.name = name; // 属性
        this.age = age;
    }

    sayHello() { // 方法
        console.log(`My name is ${this.name}`);
    }
}

// 实例化对象
const instance = new Person("Alice", 30);
instance.sayHello();

1.3 面向对象编程的四大支柱

封装

将数据(属性)和操作数据的方法绑定在一起,并限制对组件内部数据的直接访问。

  • 目的:保护内部状态,防止外部代码直接修改内部数据导致不一致,仅对外暴露必要的接口。

  • 实现:通过类的方法操作内部属性,或使用私有字段(#field)限制访问。

继承

允许一个类(子类)获取另一个类(父类)的属性和方法。

  • 目的:实现代码复用,建立类与类之间的层级关系。

  • 实现:使用 extends 关键字。

多态

指不同类的对象对同一消息(方法调用)做出不同的响应。

  • 目的:提高接口的通用性,解耦合。

  • 实现:子类重写(Override)父类的方法,使得同一方法名在不同对象实例中表现出不同的逻辑。

抽象

隐藏复杂的实现细节,只展示核心功能接口。

  • 目的:降低系统复杂度,使用户只需关注如何使用接口,而无需了解底层实现。

1.4 JavaScript OOP 的底层机制

1.4.1 语言本质

JavaScript 本质上是 基于原型(Prototype-based) 的语言,而非传统的基于类(Class-based)的语言(如 Java 或 C++)。

1.4.2 class 关键字的实质

ES6 引入的 class 关键字是语法糖。它并没有改变 JavaScript 基于原型的继承机制,只是提供了一种更清晰、更符合传统 OOP 习惯的写法。

  • 类型验证:typeof ClassName 的返回值为 'function',证明类的底层依然是函数。

1.4.3 构造函数与对象模板

在 ES6 之前,JavaScript 使用构造函数(Constructor Function)作为“对象模板”来定义类。


2. 创建对象

2.1 创建直接实例 (Object Literals)

在现代 JavaScript 中,创建单个对象最直接、最标准的方式是使用对象字面量 (Object Literal),而不是使用 new Object()。

核心语法

  • 使用花括号 {} 定义对象边界。

  • 内部包含键值对(属性和方法)。

  • 使用 const 声明对象变量,保证引用不被意外修改。

// 现代标准写法
const myNewObject = {
    // 属性 (Property)
    info: 'I am a shiny new object',

    // 方法 (Method) - 使用 ES6 方法简写语法
    showInfo() {
        // 在对象方法内部,this 指向当前对象实例
        console.log(this.info);
    }
};

// 调用方法
myNewObject.showInfo(); // 输出: "I am a shiny new object"

注意:将函数赋值给对象时,不要加括号(如 myNewObject.showInfo 是引用函数,myNewObject.showInfo() 是执行函数)。

2.2 使用关键字 this

this 是一个动态指针,它的值取决于函数的调用环境,而不是定义环境。

  1. 作为方法调用:当函数挂载在对象上并被调用时(如 obj.method()),this 指向该对象。

  2. 独立调用与严格模式:

    • 现代开发默认开启严格模式 ('use strict')(ES6 模块和 Class 中自动开启)。

    • 在严格模式下,独立调用函数(如 myFunc()),this 的值为 undefined。这避免了旧版本中 this 意外指向全局对象 (window) 导致污染全局变量的问题。

const user = {
    name: "Alice",
    greet() {
        console.log("Hello, " + this.name);
    }
};

user.greet(); // this 指向 user,输出: "Hello, Alice"

const extractedFunc = user.greet;
extractedFunc(); // 报错或无输出 (严格模式下 this 为 undefined)

2.3 使用类 (Class) 代替构造函数

当需要创建多个结构相同的对象(如多个用户、多个按钮)时,现代 JavaScript 使用 class 关键字来定义模板,而不是使用普通的函数。

  1. class 关键字:定义类的名称(通常首字母大写)。

  2. constructor 方法:类的构造函数,用于初始化对象的属性(数据)。

  3. 类体中的方法:定义对象行为。重要:写在类体中的方法会自动挂载到原型(Prototype)上,所有实例共享同一份内存,性能极佳。

// ✅ 现代标准:使用 class 定义模板
class Person {
    // 构造函数:初始化数据
    constructor(name) {
        this.name = name;
        this.info = `I am called ${this.name}`;
    }

    // 方法:定义行为 (自动共享内存)
    showInfo() {
        console.log(this.info);
    }
}

2.4 对象实例化 (Instantiation)

定义好 class 后,使用 new 关键字来创建具体的实例(Instance)。

  1. new 关键字会在内存中创建一个新对象。

  2. 自动调用 constructor 方法,初始化属性(如 this.name = 'Adam')。

  3. 将新对象与类的原型连接,使其可以使用 showInfo 等方法。

// 创建实例
const person1 = new Person('Adam');
const person2 = new Person('Eve');

// 调用各自的方法
person1.showInfo(); // 输出: I am called Adam
person2.showInfo(); // 输出: I am called Eve

// 验证方法共享 (性能优化验证)
console.log(person1.showInfo === person2.showInfo); // true

2.5 构造函数参数与解构赋值

在实例化对象时,我们通常需要传递参数来定制对象。

2.5.1 基础参数传递

直接在 constructor 中接收参数。

class Book {
    constructor(title, author) {
        this.title = title;
        this.author = author;
    }
}
const myBook = new Book("JavaScript Guide", "MDN");

2.5.2 高级参数处理:对象解构 (最佳实践)

当参数较多(超过3个)时,按顺序传递参数容易出错(如记不清是先传“年份”还是先传“颜色”)。现代开发推荐传入一个配置对象。

class Car {
    // ✅ 接收一个对象,直接解构出需要的属性,并设置默认值
    constructor({ color, make, year, mileage = 0 }) {
        this.color = color;
        this.make = make;
        this.year = year;
        this.mileage = mileage;
    }

    drive(distance) {
        this.mileage += distance;
        console.log(`New mileage: ${this.mileage}`);
    }
}

// 实例化时,无需记忆参数顺序,代码可读性极高
const myCar = new Car({
    make: "Ford",
    year: 1998,
    color: "Blue",
    mileage: 79500 // 甚至可以省略,使用默认值
});

3. 继承

“将子类 prototype 指向父类实例”的做法(Dog.prototype = new Pet())已完全被废弃。现代 JavaScript 使用 extends 关键字实现继承,语法更清晰,且避免了副作用。

  • extends:声明一个类继承自另一个类(子类继承父类)。

  • super():在子类的构造函数 (constructor) 中,必须在使用 this 之前调用 super()。它负责调用父类的构造函数,完成基础属性的初始化。

父类:Pet

定义通用的属性和方法。

class Pet {
    constructor() {
        this.animal = "";
        this.name = "";
    }

    setAnimal(newAnimal) {
        this.animal = newAnimal;
    }

    setName(newName) {
        this.name = newName;
    }
}

子类:Dog

继承 Pet 的所有功能,并添加特有的属性 breed (品种)。

class Dog extends Pet {
    constructor() {
        // 关键:调用父类构造函数,初始化 animal 和 name
        super();

        // 子类特有的属性初始化
        this.breed = "";
    }

    // 子类特有的方法
    setBreed(newBreed) {
        this.breed = newBreed;
    }

    // 演示:子类也可以重写(Override)父类方法
    setName(newName) {
        super.setName(newName); // 调用父类逻辑
        console.log(`Dog name updated to: ${this.name}`);
    }
}

// 使用继承
const myDog = new Dog();

// 1. 使用父类的方法
myDog.setAnimal("Canine");
myDog.setName("Alan");

// 2. 使用子类的方法
myDog.setBreed("Greyhound");

// 3. 验证数据
console.log(`${myDog.name} is a ${myDog.breed}`); // 输出: Alan is a Greyhound