題目
好的,我們繼續。
在上一個練習中,我們深入探討了 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 構建后端服務,這些知識都是不可或缺的基石。
恭喜你完成了這段系統的學習旅程!