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. 工厂模式
工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但允许子类决定实例化哪个类 。工厂方法让类的实例化推迟到子类中进行。
工厂模式的核心思想
- 封装对象创建逻辑:将对象的创建过程集中管理
- 解耦调用者与具体类:调用者不需要知道具体创建哪个类的实例
- 统一创建接口:通过统一的方法创建不同类型的对象
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 }
代码执行流程**
- 创建工厂实例 :
const factory = new CarFactory();
- 实例化一个汽车工厂对象
- 调用工厂方法 :
factory.createCar("suv")
- 工厂根据传入的类型"suv"执行switch-case
- 匹配到"suv"分支,创建并返回一个新的Car实例
- 新Car实例的属性为:make="Honda", model="CR-V", year=2023
- 返回汽车对象:赋值给myCar变量并打印
2. 单例模式
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。
单例模式的核心特点
- 唯一实例:保证一个类只有一个实例存在
- 全局访问:提供全局访问该实例的方式
- 延迟初始化:通常只在第一次需要时创建实例
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访问修改后的值
代码执行流程
-
第一次实例化 (
const config1 = new AppConfig()
)- 检查
AppConfig.instance
为undefined
- 初始化
this.config
对象 - 将当前实例 (
this
) 赋值给AppConfig.instance
- 返回新创建的实例
- 检查
-
第二次实例化 (
const config2 = new AppConfig()
)- 检查
AppConfig.instance
已存在 - 直接返回已保存的实例 (即
config1
) - 不执行后续的初始化代码
- 检查
-
验证单例行为
config1 === config2
返回true
,证明是同一个对象- 通过
config1
修改配置后,通过config2
能获取到修改后的值
3.观察者模式
观察者模式(Observer Pattern)是一种行为设计模式 ,它定义了一种一对多的依赖关系,当一个对象(称为"主题"或"可观察对象")的状态发生改变时,所有依赖于它的对象(称为"观察者")都会自动收到通知并更新。
观察者模式的核心概念
- Subject (主题):维护一组观察者,提供添加、删除和通知观察者的方法
- Observer (观察者):定义一个更新接口,用于接收主题的通知
- 订阅机制:观察者订阅主题,主题状态变化时通知所有订阅者
代码解析
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 收到更新: 新版本发布啦!"
代码执行流程
- 创建主题和观察者
newsPublisher
是Subject
实例,维护一个空观察者列表subscriber1
和subscriber2
是Observer
实例,各有自己的名称
- 订阅过程
- 两个观察者通过
subscribe()
方法订阅主题 - 主题的
observers
数组现在包含这两个观察者
- 两个观察者通过
- 通知过程
- 调用
newsPublisher.notify('新版本发布啦!')
- 主题遍历
observers
数组,对每个观察者调用其update()
方法 - 每个观察者执行自己的
update()
方法,打印出收到的消息
- 调用
- 取消订阅(示例中未展示但可用)
- 如需取消订阅:
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元"
六、最佳实践
- 优先使用ES6类语法:更清晰易读
- 合理使用继承:避免过深的继承链,考虑组合优于继承
- 封装私有数据:使用闭包或私有字段(#)
- 方法放在原型上:避免每个实例都创建方法副本
- 命名规范:类名使用PascalCase,实例使用camelCase
JavaScript的面向对象编程虽然与传统语言不同,但其基于原型的实现方式非常灵活。掌握这些概念后,你可以构建更复杂、更易维护的应用程序。