📌 第五节:对象与原型链:JavaScript 的“类”与“继承”
目标:理解对象创建、原型继承、this
绑定,掌握类语法与原型设计模式,实现模块化组件开发。
一、对象基础:数据的“容器”
1. 对象字面量与属性访问
语法:
const person = { name: "Alice", age: 30, greet() { // 方法简写 console.log(`Hi, I'm ${this.name}!`); }, [Symbol("id")]: "unique-123", // Symbol 属性 };
访问方式:
console.log(person.name); // "Alice"(点语法) console.log(person["age"]); // 30(方括号语法,可动态访问)
2. 对象操作
添加/修改属性:
person.job = "Engineer"; // 动态添加属性 person.age = 31; // 修改属性
删除属性:
javascript delete person.job; // 删除属性
遍历属性:
for (const key in person) { if (person.hasOwnProperty(key)) { // 排除继承属性 console.log(`${key}: ${person[key]}`); } }
3. 对象拷贝
浅拷贝:
const shallowCopy = { ...person }; // 展开运算符 // 或 Object.assign({}, person);
深拷贝:
const deepCopy = JSON.parse(JSON.stringify(person)); // 仅适用于简单对象 // 或使用 Lodash 的 `_.cloneDeep`
二、原型链:继承的“幕后英雄”
1. 原型与构造函数
__proto__
与prototype
:- 每个对象有
__proto__
指向其原型(隐式原型)。 - 函数有
prototype
属性,指向其构造的实例的原型。
function Person(name) { this.name = name; } Person.prototype.greet = function() { console.log(`Hello, I'm ${this.name}!`); }; const alice = new Person("Alice"); console.log(alice.__proto__ === Person.prototype); // true alice.greet(); // 输出:Hello, I'm Alice!
- 每个对象有
2. 原型链查找规则
查找顺序:对象自身属性 → 原型 → 原型链向上查找,直到
null
console.log(alice.toString()); // 调用 Object.prototype.toString console.log(alice.__proto__.__proto__ === Object.prototype); // true
3. 修改原型链
动态添加原型方法:
Person.prototype.sayAge = function() { console.log(`I'm ${this.age} years old.`); }; alice.age = 30; alice.sayAge(); // 输出:I'm 30 years old.
原型链陷阱:
- 共享引用类型属性可能导致意外修改。
function Animal() { this.foods = []; // 共享引用类型 } const cat = new Animal(); const dog = new Animal(); cat.foods.push("fish"); console.log(dog.foods); // ["fish"](意外共享)
三、this
绑定:上下文的“指挥官”
1. this
的四种绑定规则
规则 | 示例 | this 指向 |
---|---|---|
默认绑定 | function foo() { console.log(this); } |
全局对象(严格模式为 undefined ) |
隐式绑定 | obj.method() |
调用对象(obj ) |
显式绑定 | foo.call(obj) 或 foo.apply(obj) |
指定对象(obj ) |
new 绑定 |
new Foo() |
新创建的实例对象 |
2. 绑定优先级
显式绑定 >
new
绑定 > 隐式绑定 > 默认绑定箭头函数:继承外层作用域的
this
,无动态绑定。const obj = { name: "Bob", greet: () => console.log(`Hello, ${this.name}`), // this 为外层作用域(可能是 window/undefined) normalGreet() { console.log(`Hello, ${this.name}`); // this 为 obj }, }; obj.greet(); // 输出:Hello, undefined(箭头函数无自身 this) obj.normalGreet(); // 输出:Hello, Bob
3. 常见问题与解决方案
回调函数中的
this
丢失:const button = { text: "Click me", handleClick() { console.log(`Button clicked: ${this.text}`); }, }; document.querySelector("button").onclick = button.handleClick; // this 丢失,指向 button 元素
解决方案:
使用箭头函数包裹:
document.querySelector("button").onclick = () => button.handleClick();
或显式绑定:
document.querySelector("button").onclick = button.handleClick.bind(button);
四、类语法:ES6 的“语法糖”
1. 类定义与继承
基础类:
class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log(`Hi, I'm ${this.name}!`); } } const bob = new Person("Bob", 25); bob.greet(); // 输出:Hi, I'm Bob!
继承:
class Student extends Person { constructor(name, age, major) { super(name, age); // 调用父类构造函数 this.major = major; } study() { console.log(`${this.name} is studying ${this.major}.`); } } const charlie = new Student("Charlie", 20, "Math"); charlie.greet(); // 输出:Hi, I'm Charlie! charlie.study(); // 输出:Charlie is studying Math.
2. 类方法与静态方法
实例方法:
class Dog { bark() { console.log("Woof!"); } }
静态方法(直接通过类调用):
class MathUtils { static add(a, b) { return a + b; } } console.log(MathUtils.add(2, 3)); // 5
3. Getter 与 Setter
控制属性访问:
class Circle { constructor(radius) { this._radius = radius; // 约定前缀 `_` 表示内部属性 } get radius() { return this._radius; } set radius(newRadius) { if (newRadius > 0) { this._radius = newRadius; } else { console.error("Radius must be positive!"); } } } const circle = new Circle(5); console.log(circle.radius); // 5 circle.radius = 10; // 调用 setter circle.radius = -1; // 输出错误信息
五、实战案例:图书管理系统
目标:实现一个图书类,支持添加、删除、搜索图书,并统计库存。
代码实现:
// 1. 定义图书类
class Book {
constructor(title, author, quantity) {
this.title = title;
this.author = author;
this.quantity = quantity;
}
updateQuantity(delta) {
this.quantity += delta;
if (this.quantity < 0) {
console.error("库存不足!");
this.quantity = 0;
}
}
}
// 2. 定义书店类(继承)
class Bookstore {
constructor() {
this.books = [];
}
addBook(book) {
this.books.push(book);
}
removeBook(title) {
this.books = this.books.filter((book) => book.title !== title);
}
searchByAuthor(author) {
return this.books.filter((book) => book.author === author);
}
getTotalInventory() {
return this.books.reduce((sum, book) => sum + book.quantity, 0);
}
}
// 3. 使用示例
const jsBook = new Book("JavaScript Guide", "Alice", 10);
const pythonBook = new Book("Python Basics", "Bob", 5);
const store = new Bookstore();
store.addBook(jsBook);
store.addBook(pythonBook);
jsBook.updateQuantity(-3); // 减少库存
console.log(store.getTotalInventory()); // 输出:12
console.log(store.searchByAuthor("Alice")); // 输出:[{ title: "JavaScript Guide", ... }]
六、常见问题与最佳实践
1. 原型继承 vs 类继承
特性 | 原型继承 | 类继承(ES6) |
---|---|---|
语法 | 显式操作原型链 | 声明式语法,更接近传统 OOP |
可读性 | 较低(需理解原型链) | 更高(语法糖) |
适用场景 | 底层原理学习、性能优化 | 现代项目开发 |
2. 避免滥用继承
组合优于继承:优先使用对象组合而非深度继承。
// 组合示例:通过属性引入功能 const loggerMixin = { log() { console.log(`${this.name}: Action performed`); }, }; class User { constructor(name) { this.name = name; } } Object.assign(User.prototype, loggerMixin); // 组合功能
3. 性能优化
缓存原型方法
:减少频繁查找原型链。
const hasOwnProperty = Object.prototype.hasOwnProperty; function isProperty(obj, key) { return hasOwnProperty.call(obj, key); // 避免在循环中重复查找 }
七、课后任务
基础任务
- 创建一个
Animal
类,支持eat
和sleep
方法,并继承一个Dog
子类。 - 用类语法实现一个“银行账户”模块,支持存款、取款、查询余额。
- 封装一个“防篡改”对象,禁止修改或删除属性(使用
Object.freeze
或 Proxy)。
进阶任务
- 设计一个“任务队列”类,支持并发控制(限制同时执行的任务数)。
- 实现一个“观察者模式”类,支持多个监听器订阅和通知。
- 用 Proxy 实现一个“数据验证”类,拦截对象的属性设置操作。
📌 关键点总结
- 对象基础:
- 掌握对象字面量、属性访问、拷贝与遍历。
- 原型链与继承:
- 理解
__proto__
、prototype
、原型链查找规则,避免原型链陷阱。
- 理解
this
绑定:- 区分四种绑定规则,优先使用箭头函数或显式绑定。
- 类语法:
- 熟练定义类、继承、静态方法、Getter/Setter,区分类继承与组合。
🎯 下一节预告
「异步编程:从回调地狱到 Async/Await」
- 破解回调地狱,掌握 Promise、
async/await
,实现一个“天气查询”应用。
如果需要补充更多案例(如设计模式应用、Object.create
手动实现继承、Proxy 高级用法等),或调整内容深度,请随时告知! 😊