js代码09
题目
好的,我们继续。
在上一个练习中,我们深入探讨了 this
的复杂性。你会发现,ES6 引入的 class
语法在很大程度上就是为了简化 this
的使用,并为 JavaScript 提供一个更清晰、更熟悉的面向对象编程(OOP)模型。
class
本质上是 JavaScript 现有原型继承模型的“语法糖”。它没有改变 JavaScript 的底层机制,但提供了一套非常清晰和友好的语法来创建“对象的蓝图”。
练习 09: 对象的蓝图 - class 语法与面向对象编程
在这个练习中,我们将创建一个 BankAccount
类来模拟一个银行账户,它有自己的属性(如余额)和方法(如存款、取款)。然后,我们还会创建一个更具体的 SavingsAccount
(储蓄账户)类来继承它。
🎯 学习目标:
- 学会使用
class
关键字定义一个类。 - 理解并使用
constructor
方法来初始化对象实例的属性。 - 在类中定义方法,并理解在这些方法中
this
会自动指向实例对象。 - 使用
new
关键字来创建类的实例。 - (进阶) 使用
extends
关键字实现类的继承。 - (进阶) 在子类中使用
super()
来调用父类的构造函数。
🛠️ 任务:
Part 1: 创建一个基础的 BankAccount
类
- 构造函数 (
constructor
): 应该接受ownerName
和一个可选的初始balance
(默认值为 0)作为参数。 deposit(amount)
方法: 增加账户余额,并打印一条确认信息。withdraw(amount)
方法: 减少账户余额,但需要检查余额是否充足。balance
getter: 创建一个名为balance
的“计算属性”,用于安全地获取当前余额。
Part 2 (挑战): 创建一个 SavingsAccount
子类
- 这个类应该继承自
BankAccount
。 - 它的
constructor
应该额外接受一个interestRate
(利率) 参数。 - 它应该有一个新方法
applyInterest()
,用于计算利息并将其存入账户。
📋 初始代码 (09-classes.js
):
创建新文件 09-classes.js
,并将以下代码复制进去。你的任务是补全所有 // TODO
的部分。
// --- Part 1: 创建一个基础的银行账户类 ---
class BankAccount {// 1. 添加一个 constructor// 它应该接受 ownerName 和 balance (默认值为 0)// 为了演示 getter,我们将实际余额存在一个通常表示“私有”的 _balance 变量上。constructor(ownerName, balance = 0) {// TODO: 在这里设置 ownerName 和 _balance 属性}// 2. 添加 deposit 方法deposit(amount) {// TODO: 增加 _balance 的值,并打印新余额}// 3. 添加 withdraw 方法withdraw(amount) {// TODO: 检查 _balance 是否足够,如果足够则减去金额并打印,否则打印警告}// 4. 使用 getter 来创建一个名为 balance 的只读属性get balance() {// TODO: 返回 _balance 的值}
}// --- 使用 BankAccount 类 ---
console.log("--- 操作基础账户 ---");
const myAccount = new BankAccount("Jack", 500);
console.log(`Account created for ${myAccount.ownerName}.`);
// myAccount.deposit(100);
// myAccount.withdraw(200);
// console.log(`Final balance for Jack: ${myAccount.balance}`); // 注意这里是调用 getter,而不是 myAccount._balance
// myAccount.withdraw(500); // 应该会提示余额不足// --- Part 2: (挑战) 创建一个储蓄账户子类 ---
class SavingsAccount /* TODO: 使用 extends 继承 BankAccount */ {// 5. 添加 constructor// 它应该接受 ownerName, balance, 和 interestRate。constructor(ownerName, balance = 0, interestRate) {// TODO: 使用 super() 调用父类的构造函数来设置 ownerName 和 balance// TODO: 设置自己的 interestRate 属性}// 6. 添加 applyInterest 方法applyInterest() {// TODO: 根据 interestRate 计算利息,然后调用父类的 deposit 方法将利息存入。}
}// --- 使用 SavingsAccount 类 ---
console.log("\n--- 操作储蓄账户 ---");
const mySavings = new SavingsAccount("Jill", 1000, 0.05); // 5% 的利率
console.log(`Savings account created for ${mySavings.ownerName}.`);
// mySavings.applyInterest();
// console.log(`Final savings balance for Jill: ${mySavings.balance}`);
// mySavings.withdraw(1100); // 应该会提示余额不足
✅ 预期输出:
--- 操作基础账户 ---
Account created for Jack.
New balance: 600
New balance: 400
Final balance for Jack: 400
Insufficient funds! Current balance is 400.--- 操作储蓄账户 ---
Savings account created for Jill.
Interest applied. New balance: 1050
Final savings balance for Jill: 1050
Insufficient funds! Current balance is 1050.
完成这个练习后,你将对如何在 JavaScript 中以结构化的方式创建和管理复杂对象有一个非常清晰的认识。这也是通往所有现代前端框架(它们都大量使用基于类的组件)的必经之路。
答案
好的,我们来看这个 class
练习的完整答案。
这部分代码集中体现了面向对象编程(OOP)的核心思想:封装、继承。
参考实现 (09-classes.js
)
// --- Part 1: 创建一个基础的银行账户类 ---
class BankAccount {// 1. 构造函数:当使用 new 创建实例时,它会被自动调用constructor(ownerName, balance = 0) {// `this` 指向新创建的实例对象this.ownerName = ownerName;// 使用下划线 `_` 是一种约定,表示这个属性不应该被外部直接访问this._balance = balance; }// 2. deposit 方法:类的方法deposit(amount) {if (amount > 0) {this._balance += amount;console.log(`New balance: ${this._balance}`);} else {console.log("Deposit amount must be positive.");}}// 3. withdraw 方法withdraw(amount) {if (amount > this._balance) {console.log(`Insufficient funds! Current balance is ${this._balance}.`);} else if (amount > 0) {this._balance -= amount;console.log(`New balance: ${this._balance}`);} else {console.log("Withdrawal amount must be positive.");}}// 4. getter:它让我们可以像访问属性一样调用一个函数get balance() {return this._balance;}
}// --- 使用 BankAccount 类 ---
console.log("--- 操作基础账户 ---");
const myAccount = new BankAccount("Jack", 500);
console.log(`Account created for ${myAccount.ownerName}.`);
myAccount.deposit(100);
myAccount.withdraw(200);
// 注意这里我们访问 .balance,而不是 ._balance。它会自动调用 get balance() 方法。
console.log(`Final balance for Jack: ${myAccount.balance}`);
myAccount.withdraw(500);// --- Part 2: (挑战) 创建一个储蓄账户子类 ---
// extends 关键字用于声明一个类是另一个类的子类
class SavingsAccount extends BankAccount {// 5. 子类的构造函数constructor(ownerName, balance = 0, interestRate) {// `super()` 必须在子类构造函数中使用 `this` 之前被调用。// 它会调用父类(BankAccount)的构造函数,并传递参数。super(ownerName, balance); // 这是子类自己独有的属性this.interestRate = interestRate;}// 6. 子类自己独有的方法applyInterest() {const interest = this._balance * this.interestRate;console.log("Interest applied.");// 调用从父类继承来的 deposit 方法this.deposit(interest);}
}// --- 使用 SavingsAccount 类 ---
console.log("\n--- 操作储蓄账户 ---");
const mySavings = new SavingsAccount("Jill", 1000, 0.05); // 5% 的利率
console.log(`Savings account created for ${mySavings.ownerName}.`);
mySavings.applyInterest();
console.log(`Final savings balance for Jill: ${mySavings.balance}`);
mySavings.withdraw(1100);
代码解析
-
constructor(ownerName, balance = 0)
- 当你写
new BankAccount(...)
时,constructor
会被自动执行。 this.ownerName = ownerName;
这行代码是在新创建的实例对象上设置一个名为ownerName
的属性。在类的方法中,this
的指向通常就是我们所期望的实例对象,这比之前练习中的情况要简单得多。
- 当你写
-
get balance()
- 这是一个 getter。它定义了一个“访问器属性”。从外部看,你可以像读取普通属性一样读取它(
myAccount.balance
),但实际上,每次读取时都会执行这个函数。这是一种封装的体现——外部只能读取余额,但不能直接设置_balance
的值。
- 这是一个 getter。它定义了一个“访问器属性”。从外部看,你可以像读取普通属性一样读取它(
-
class SavingsAccount extends BankAccount
extends
关键字建立了SavingsAccount
和BankAccount
之间的继承关系。SavingsAccount
自动获得了BankAccount
的所有方法(如deposit
,withdraw
)和属性(通过super
调用设置)。
-
super(ownerName, balance)
- 在子类的
constructor
中,super
是一个对父类constructor
的引用。 - 必须先调用
super()
来完成父类的初始化工作,然后才能在子类构造函数中使用this
关键字来添加子类自己的属性(如this.interestRate
)。
- 在子类的
-
方法继承 (
this.deposit(interest)
)- 在
applyInterest
方法中,我们直接调用了this.deposit()
。 SavingsAccount
本身没有定义deposit
方法,于是 JavaScript 会沿着原型链向上查找,在父类BankAccount
中找到了这个方法,并成功调用。这就是继承在起作用。
- 在
至此,我们已经完整地学习了 JavaScript 语言从基础到核心的各个重要组成部分:
- 数据处理(函数式方法)
- 异步流程(回调、Promise、async/await)
- 现代语法(解构、展开/剩余)
- 代码组织(模块化)
- 面向对象(
this
、class
、继承)
你现在已经拥有了构建复杂 JavaScript 应用程序所需的全部核心知识。无论是去学习 React/Vue/Angular 这样的前端框架,还是使用 Node.js 构建后端服务,这些知识都是不可或缺的基石。
恭喜你完成了这段系统的学习旅程!