JavaScript - 封裝

你好,初露頭角的程式設計師們!今天,我們將踏上一段令人興奮的旅程,探索JavaScript封裝的神秘世界。如果你是程式設計的新手,不用擔心——我將成為你友善的導遊,我們將一起逐步探索這個概念。所以,拿起你的虛擬背包,讓我們開始吧!

JavaScript - Encapsulation

什麼是封裝?

想像你有一個寶藏箱。你不想讓任何人隨意打開它並拿走你珍貴的寶石,對吧?這就是封裝在程式設計中的作用。它就像把你的代碼放在一個保護性的氣泡中,只允許外部訪問特定的部分。

簡單來說,封裝就是將數據和操作這些數據的方法打包在同一個單元或對象中。這是一種隱藏對象內部工作細節的方式,只暴露必要的信息。

為什麼需要封裝?

你可能會想,“我們為什麼需要這個花哨的封裝東西?”讓我給你講一個小故事。

從前,在一個意大利麵條代碼的國度裡,住著一個名叫Bob的程式設計師。Bob的代碼對任何人都可以更改,很快,其他程式設計師開始干涉他的代碼。混亂隨之而來!沒有人知道代碼的哪部分是做什麼的,bug像兔子一樣繁殖。

這就是封裝拯救我們的地方。它幫助我們:

  1. 保護我們的數據不受意外修改
  2. 隱藏複雜的實現細節
  3. 讓我們的代碼更有組織,更容易維護
  4. 减少代碼不同部分之間的依賴

在JavaScript中實現封裝的不同方法

在JavaScript中,我們有幾個小花招來實現封裝。讓我們一一探索:

使用函數閉包實現封裝

閉包在JavaScript中就像神奇的氣泡,它記住了創建的環境。我們可以使用它來創建私有變量和方法。讓我們看一個例子:

function createBankAccount(initialBalance) {
let balance = initialBalance;

return {
deposit: function(amount) {
balance += amount;
console.log(`存入 ${amount}。新的餘額是 ${balance}`);
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
console.log(`提取 ${amount}。新的餘額是 ${balance}`);
} else {
console.log("資金不足!");
}
},
getBalance: function() {
return balance;
}
};
}

const myAccount = createBankAccount(1000);
myAccount.deposit(500);  // 存入 500。新的餘額是 1500
myAccount.withdraw(200); // 提取 200。新的餘額是 1300
console.log(myAccount.getBalance()); // 1300
console.log(myAccount.balance); // undefined

在這個例子中,balance是我們的私有變量。它不能直接從createBankAccount函數外部訪問。我們只能通過我們暴露的方法來與它交互:depositwithdrawgetBalance。這就是封裝在行動!

使用ES6類和私有變量實現封裝

隨著ES6類的引入,我們得到了一種更熟悉的創建對象和實現封裝的方式。這是我們如何操作的:

class BankAccount {
#balance;  // 私有字段

constructor(initialBalance) {
this.#balance = initialBalance;
}

deposit(amount) {
this.#balance += amount;
console.log(`存入 ${amount}。新的餘額是 ${this.#balance}`);
}

withdraw(amount) {
if (amount <= this.#balance) {
this.#balance -= amount;
console.log(`提取 ${amount}。新的餘額是 ${this.#balance}`);
} else {
console.log("資金不足!");
}
}

getBalance() {
return this.#balance;
}
}

const myAccount = new BankAccount(1000);
myAccount.deposit(500);  // 存入 500。新的餘額是 1500
myAccount.withdraw(200); // 提取 200。新的餘額是 1300
console.log(myAccount.getBalance()); // 1300
console.log(myAccount.#balance); // SyntaxError: 私有字段 '#balance' 必須在封閉的類中聲明

在這個例子中,我們使用#符號將balance聲明為私有字段。這意味著它只能在BankAccount類內部訪問,而不能從外部訪問。

使用getter和setter實現封裝

getter和setter是特殊的函數,允許我們定義如何訪問或修改屬性。它們就像我們對象屬性的保鏢。讓我們看看它們是如何工作的:

class Circle {
constructor(radius) {
this._radius = radius;
}

get radius() {
return this._radius;
}

set radius(value) {
if (value <= 0) {
throw new Error("半徑必須為正數");
}
this._radius = value;
}

get area() {
return Math.PI * this._radius ** 2;
}
}

const myCircle = new Circle(5);
console.log(myCircle.radius); // 5
console.log(myCircle.area);   // 78.53981633974483

myCircle.radius = 10;
console.log(myCircle.radius); // 10

myCircle.radius = -1; // Error: 半徑必須為正數

在這個例子中,我們使用getter來讀取radius,使用setter來修改它。setter中包括了一個檢查,確保半徑始終為正數。我們還有一個area的getter,它會在運行時計算面積。

JavaScript中封裝的優勢

現在我們已經看到了如何實現封裝,讓我們總結一下它的優勢:

  1. 數據保護:它防止未授權訪問我們對象的內部。
  2. 靈活性:我們可以更改內部實現,而不影響使用我們對象的外部代碼。
  3. 模塊化:它幫助創建自包含的代碼單元,使代碼更模塊化,更容易管理。
  4. 調試:通過限制數據可以被修改的地方,更容易追蹤bug。
  5. 抽象:它讓我們能夠隱藏複雜的實現細節,並為使用我們對象提供簡單的接口。

方法表

這裡有一個方便的表格,總結了我們討論過的封裝方法:

方法 描述 示例
函數閉包 使用閉包創建私有變量 function createObject() { let privateVar = 0; return { getVar: () => privateVar }; }
ES6類與私有字段 使用#在類中聲明私有字段 class MyClass { #privateField; constructor() { this.#privateField = 0; } }
Getter和Setter 使用特殊方法來控制屬性的訪問 class MyClass { get prop() { return this._prop; } set prop(value) { this._prop = value; } }

這就是全部,各位!我們已經穿越了封裝的土地,從基本概念到在JavaScript中實現它的不同方法。記住,封裝就像一個好的保密者——它知道什麼該分享,什麼該保密。在你繼續你的程式設計冒險時,你會發現封裝是一個值得信賴的伴侶,幫助你創建有強度和可維護性的代碼。

持續練習,保持好奇心,並且快樂編程!

Credits: Image by storyset