JavaScript 对象与面向对象编程详解

JavaScript 对象与面向对象编程详解

JavaScript 的面向对象编程(OOP)与传统基于类的语言(如Java)有所不同,它主要基于**原型(prototype)**实现。下面我将通过概念解释和实例演示来全面讲解 JavaScript 中的对象与OOP。

一、对象基础

1. 对象定义与创建

对象是键值对的集合,用于描述现实世界中的实体。

(1) 对象字面量(最常用)
javascript 复制代码
const user = {
  // 数据属性
  username: 'jsMaster',
  level: 'advanced',
  
  // 访问器属性(getter/setter)
  get description() {
    return `${this.username} [${this.level}]`;
  },
  set levelUp(newLevel) {
    if (['beginner', 'intermediate', 'advanced'].includes(newLevel)) {
      this.level = newLevel;
    }
  },
  
  // 方法
  showProfile() {
    console.log(this.description);
  }
};

console.log(user.description); // "jsMaster [advanced]"
user.levelUp = 'expert';      // 无效,因为不在允许值中
user.showProfile();           // "jsMaster [advanced]"
(2) 使用 new Object()
ini 复制代码
const car = new Object();
car.brand = "Toyota";
car.model = "Camry";
(3) 使用 Object.create()
ini 复制代码
const personPrototype = {
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

const person = Object.create(personPrototype);
person.name = 'Alice';  // 添加实例属性
person.greet();         // "Hello, I'm Alice
(4) 构造函数模式
javascript 复制代码
function Book(title, author, year) {
  // 实例属性
  this.title = title;
  this.author = author;
  this.year = year;
  
  // 实例方法(不推荐,每个实例都会创建方法副本)
  this.getInfo = function() {
    return `${this.title} by ${this.author}, ${this.year}`;
  };
}

// 原型方法(推荐)
Book.prototype.getAge = function() {
  const years = new Date().getFullYear() - this.year;
  return `${this.title} is ${years} years old`;
};

const book1 = new Book('JS Patterns', 'John Doe', 2015);
console.log(book1.getInfo());  // "JS Patterns by John Doe, 2015"
console.log(book1.getAge());   // "JS Patterns is 8 years old"

2. 属性描述符详解

JavaScript 对象属性实际上有更精细的控制方式:

javascript 复制代码
const obj = {};

Object.defineProperty(obj, 'readOnlyProp', {
  value: 42,
  writable: false,       // 不可写
  enumerable: true,     // 可枚举
  configurable: false    // 不可配置(不能删除或修改属性描述符)
});

console.log(obj.readOnlyProp); // 42
obj.readOnlyProp = 100;        // 静默失败(严格模式下会报错)
console.log(obj.readOnlyProp); // 42

// 批量定义属性
Object.defineProperties(obj, {
  prop1: {
    value: 'hello',
    writable: true
  },
  prop2: {
    get() { return this.prop1.toUpperCase(); },
    enumerable: true
  }
});

3. 对象属性操作

arduino 复制代码
const book = {
  title: "JavaScript高级编程",
  author: "Nicholas C. Zakas"
};

// 访问属性
console.log(book.title); // "JavaScript高级编程"
console.log(book["author"]); // "Nicholas C. Zakas"

// 添加/修改属性
book.publishedYear = 2020;
book["pageCount"] = 700;

// 删除属性
delete book.pageCount;

// 检查属性是否存在
"title" in book; // true
book.hasOwnProperty("title"); // true

二、面向对象编程核心概念

1. 构造函数与new关键字

构造函数用于创建特定类型的对象。

javascript 复制代码
function Product(name, price) {
  // 实例属性
  this.name = name;
  this.price = price;
  
 // 实例方法(不推荐,每个实例都会创建方法副本)
  this.displayInfo = function() {
    console.log(`${this.name} 售价 ${this.price}元`);
  };
}

// 使用new创建实例
const iphone = new Product("iPhone 13", 5999);
const macbook = new Product("MacBook Pro", 12999);

iphone.displayInfo(); // "iPhone 13 售价 5999元"

2. 原型(prototype)

JavaScript 使用原型实现继承,每个函数都有prototype属性。

javascript 复制代码
function Person(name) {
  this.name = name;
}

// 通过原型添加方法(所有实例共享)
Person.prototype.greet = function() {
  console.log(`你好,我是${this.name}`);
};

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

p1.greet(); // "你好,我是李四"
p2.greet(); // "你好,我是王五"

// 检查原型关系
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true

3. 原型链继承

javascript 复制代码
// 父类
function Animal(name) {
  this.name = name;
}
Animal.prototype.eat = function() {
  console.log(`${this.name}正在吃东西`);
};

// 子类
function Dog(name, breed) {
  Animal.call(this, name); // 1. 调用父类构造函数
  this.breed = breed;
}

// 2.设置原型链
Dog.prototype = Object.create(Animal.prototype);
// 3. 修复constructor指向
Dog.prototype.constructor = Dog;

// 子类方法
Dog.prototype.bark = function() {
  console.log("汪汪!");
};

const myDog = new Dog("阿黄", "金毛");
myDog.eat(); // "阿黄正在吃东西"
myDog.bark(); // "汪汪!"

三、ES6类语法

ES6引入了class语法糖,使OOP更直观。

1. 基本类定义

arduino 复制代码
class Rectangle {
  // 构造函数
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  
  // 方法
  calcArea() {
    return this.height * this.width;
  }
  
  // Getter
  get area() {
    return this.calcArea();
  }
  
  // Static方法
  static info() {
    return "我是矩形类";
  }
}

const rect = new Rectangle(10, 20);
console.log(rect.area); // 200
console.log(Rectangle.info()); // "我是矩形类"

2. 继承

javascript 复制代码
class Vehicle {
  constructor(make, model) {
    this.make = make;
    this.model = model;
    this.speed = 0;
  }
  
  accelerate(amount) {
    this.speed += amount;
    console.log(`${this.make} ${this.model}加速到${this.speed}km/h`);
  }
  
  static info() {
    return '这是一个交通工具类';
  }
}

class Car extends Vehicle {
  constructor(make, model, doors) {
    super(make, model);  // 必须首先调用super()
    this.doors = doors;
  }
  
  // 方法重写
  accelerate(amount) {
    super.accelerate(amount);  // 调用父类方法
    console.log('小心驾驶!');
  }
  
  // 新方法
  honk() {
    console.log('Beep beep!');
  }
}

const myCar = new Car('Toyota', 'Camry', 4);
myCar.accelerate(30);
// 输出:
// "Toyota Camry加速到30km/h"
// "小心驾驶!"

四、面向对象特性实现

1. 封装

kotlin 复制代码
class BankAccount {
  #balance = 0; // 私有字段(ES2022)
  
  constructor(initialBalance) {
    this.#balance = initialBalance;
  }
  
  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
    return this.#balance;
  }
  
  withdraw(amount) {
    if (amount > 0 && amount <= this.#balance) {
      this.#balance -= amount;
      return amount;
    }
    return 0;
  }
  
  get balance() {
    return this.#balance;
  }
}

const account = new BankAccount(1000);
console.log(account.balance); // 1000
account.deposit(500);
console.log(account.balance); // 1500
// account.#balance = 10000; // 报错: 私有字段不可访问
(1) 使用闭包实现私有变量
javascript 复制代码
function Counter() {
  // 私有变量
  let count = 0;
  
  // 特权方法
  this.increment = function() {
    count++;
    return count;
  };
  
  this.getCount = function() {
    return count;
  };
}

const counter = new Counter();
console.log(counter.increment()); // 1
console.log(counter.count);       // undefined (无法直接访问)
(2) ES2022 私有类字段
javascript 复制代码
class User {
  // 私有字段
  #password;
  
  constructor(username, password) {
    this.username = username;
    this.#password = password;
  }
  
  // 私有方法
  #validatePassword(pwd) {
    return pwd === this.#password;
  }
  
  login(password) {
    if (this.#validatePassword(password)) {
      console.log('登录成功');
    } else {
      console.log('密码错误');
    }
  }
}

const user = new User('admin', '123456');
user.login('123456');  // "登录成功"
// user.#password;     // 语法错误

2. 多态

scala 复制代码
class Shape {
  area() {
    return 0;
  }
}

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

class Square extends Shape {
  constructor(side) {
    super();
    this.side = side;
  }
  
  area() {
    return this.side ** 2;
  }
}

const shapes = [new Circle(5), new Square(4)];
shapes.forEach(shape => {
  console.log(`面积: ${shape.area()}`);
});
// 输出:
// "面积: 78.53981633974483"
// "面积: 16"

五、设计模式实例

1. 工厂模式

工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但允许子类决定实例化哪个类工厂方法让类的实例化推迟到子类中进行。

工厂模式的核心思想

  1. 封装对象创建逻辑:将对象的创建过程集中管理
  2. 解耦调用者与具体类:调用者不需要知道具体创建哪个类的实例
  3. 统一创建接口:通过统一的方法创建不同类型的对象
typescript 复制代码
// 1. 定义产品类 - Car
class Car {
  constructor(make, model, year) {
    this.make = make;    // 制造商
    this.model = model;  // 型号
    this.year = year;    // 年份
  }
}

// 2. 定义工厂类 - CarFactory
class CarFactory {
  // 工厂方法,根据类型创建不同的Car实例
  createCar(type) {
    switch(type) {
      case "sedan":
        return new Car("Toyota", "Camry", 2023);
      case "suv":
        return new Car("Honda", "CR-V", 2023);
      case "electric":
        return new Car("Tesla", "Model 3", 2023);
      default:
        throw new Error("未知的汽车类型");
    }
  }
}

// 3. 使用工厂创建对象
const factory = new CarFactory();
const myCar = factory.createCar("suv");
console.log(myCar); // 输出: Car { make: "Honda", model: "CR-V", year: 2023 }

代码执行流程**

  1. 创建工厂实例const factory = new CarFactory();
    • 实例化一个汽车工厂对象
  2. 调用工厂方法factory.createCar("suv")
    • 工厂根据传入的类型"suv"执行switch-case
    • 匹配到"suv"分支,创建并返回一个新的Car实例
    • 新Car实例的属性为:make="Honda", model="CR-V", year=2023
  3. 返回汽车对象:赋值给myCar变量并打印

2. 单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例

单例模式的核心特点

  1. 唯一实例:保证一个类只有一个实例存在
  2. 全局访问:提供全局访问该实例的方式
  3. 延迟初始化:通常只在第一次需要时创建实例
javascript 复制代码
class AppConfig {
  constructor() {
    // 检查是否已经存在实例
    if (AppConfig.instance) {
      // 如果已存在,则返回现有实例
      return AppConfig.instance;
    }
  
    // 初始化配置对象
    this.config = {
      apiUrl: "https://api.example.com",
      timeout: 5000,
      env: "production"
    };
  
    // 将当前实例保存到类的静态属性中
    AppConfig.instance = this;
  }
  
  // 获取配置项的方法
  getConfig(key) {
    return this.config[key];
  }
  
  // 设置配置项的方法
  setConfig(key, value) {
    this.config[key] = value;
  }
}

// 测试单例行为
const config1 = new AppConfig();
const config2 = new AppConfig();

console.log(config1 === config2); // true,证明是同一个实例
config1.setConfig("timeout", 3000); // 通过config1修改配置
console.log(config2.getConfig("timeout")); // 3000,通过config2访问修改后的值

代码执行流程

  1. 第一次实例化 (const config1 = new AppConfig())

    • 检查 AppConfig.instanceundefined
    • 初始化 this.config 对象
    • 将当前实例 (this) 赋值给 AppConfig.instance
    • 返回新创建的实例
  2. 第二次实例化 (const config2 = new AppConfig())

    • 检查 AppConfig.instance 已存在
    • 直接返回已保存的实例 (即 config1)
    • 不执行后续的初始化代码
  3. 验证单例行为

    • config1 === config2 返回 true,证明是同一个对象
    • 通过 config1 修改配置后,通过 config2 能获取到修改后的值

3.观察者模式

观察者模式(Observer Pattern)是一种行为设计模式 ,它定义了一种一对多的依赖关系,当一个对象(称为"主题"或"可观察对象")的状态发生改变时,所有依赖于它的对象(称为"观察者")都会自动收到通知并更新。

观察者模式的核心概念

  1. Subject (主题):维护一组观察者,提供添加、删除和通知观察者的方法
  2. Observer (观察者):定义一个更新接口,用于接收主题的通知
  3. 订阅机制:观察者订阅主题,主题状态变化时通知所有订阅者

代码解析

javascript 复制代码
// 1. 主题类 - 维护观察者列表并负责通知
class Subject {
  constructor() {
    this.observers = []; // 存储观察者列表
  }

  // 订阅方法:添加观察者
  subscribe(observer) {
    this.observers.push(observer);
  }

  // 取消订阅方法:移除观察者
  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  // 通知方法:向所有观察者发送更新
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

// 2. 观察者类 - 定义如何响应主题的更新
class Observer {
  constructor(name) {
    this.name = name; // 观察者标识
  }

  // 更新方法:接收主题通知时执行的操作
  update(data) {
    console.log(`${this.name} 收到更新: ${data}`);
  }
}

// 3. 使用示例
const newsPublisher = new Subject(); // 创建主题(新闻发布者)

const subscriber1 = new Observer('用户A'); // 创建观察者1
const subscriber2 = new Observer('用户B'); // 创建观察者2

// 观察者订阅主题
newsPublisher.subscribe(subscriber1);
newsPublisher.subscribe(subscriber2);

// 主题状态变化,通知所有观察者
newsPublisher.notify('新版本发布啦!');
// 输出:
// "用户A 收到更新: 新版本发布啦!"
// "用户B 收到更新: 新版本发布啦!"

代码执行流程

  1. 创建主题和观察者
    • newsPublisherSubject 实例,维护一个空观察者列表
    • subscriber1subscriber2Observer 实例,各有自己的名称
  2. 订阅过程
    • 两个观察者通过 subscribe() 方法订阅主题
    • 主题的 observers 数组现在包含这两个观察者
  3. 通知过程
    • 调用 newsPublisher.notify('新版本发布啦!')
    • 主题遍历 observers 数组,对每个观察者调用其 update() 方法
    • 每个观察者执行自己的 update() 方法,打印出收到的消息
  4. 取消订阅(示例中未展示但可用)
    • 如需取消订阅:newsPublisher.unsubscribe(subscriber1)

4. 策略模式实现

javascript 复制代码
// 策略对象
const paymentStrategies = {
  wechatPay(amount) {
    console.log(`微信支付: 支付${amount}元`);
    // 实际支付逻辑...
  },

  aliPay(amount) {
    console.log(`支付宝支付: 支付${amount}元`);
    // 实际支付逻辑...
  },

  creditCard(amount) {
    console.log(`信用卡支付: 支付${amount}元`);
    // 实际支付逻辑...
  }
};

// 上下文类
class PaymentProcessor {
  constructor(strategy = 'wechatPay') {
    this.strategy = paymentStrategies[strategy];
  }

  setStrategy(strategy) {
    this.strategy = paymentStrategies[strategy];
  }

  pay(amount) {
    if (!this.strategy) {
      throw new Error('未设置支付策略');
    }
    this.strategy(amount);
  }
}

// 使用示例
const payment = new PaymentProcessor();
payment.pay(100); // "微信支付: 支付100元"

payment.setStrategy('creditCard');
payment.pay(200); // "信用卡支付: 支付200元"

六、最佳实践

  1. 优先使用ES6类语法:更清晰易读
  2. 合理使用继承:避免过深的继承链,考虑组合优于继承
  3. 封装私有数据:使用闭包或私有字段(#)
  4. 方法放在原型上:避免每个实例都创建方法副本
  5. 命名规范:类名使用PascalCase,实例使用camelCase

JavaScript的面向对象编程虽然与传统语言不同,但其基于原型的实现方式非常灵活。掌握这些概念后,你可以构建更复杂、更易维护的应用程序。

相关推荐
RPGMZ24 分钟前
RPGMZ游戏引擎之如何设计每小时开启一次的副本
javascript·游戏·游戏引擎·rpgmz
RPGMZ25 分钟前
RPGMZ游戏引擎 如何手动控制文字显示速度
开发语言·javascript·游戏引擎·rpgmz
Codebee33 分钟前
OneCode核心概念解析——View(视图)
前端·人工智能
GIS之路33 分钟前
GIS 数据质检:验证 Geometry 有效性
前端
GIS之路38 分钟前
GeoJSON 数据简介
前端
今阳38 分钟前
鸿蒙开发笔记-16-应用间跳转
android·前端·harmonyos
前端小饭桌39 分钟前
CSS属性值太多记不住?一招教你搞定
前端·css
快起来别睡了40 分钟前
深入浏览器底层原理:从输入URL到页面显示全过程解析
前端·架构
阿星做前端42 分钟前
一个倒计时功能引发的线上故障
前端·javascript·react.js
莯炗43 分钟前
CSS知识补充 --- 控制继承
前端·css·css继承·css控制继承