Javascript|Getter 與 Setter 完整解析
更新日期: 2024 年 4 月 10 日
基本概念
在開始談論 setter 和 getter 之前,先來瞭解一下 JavaScript 物件裡面的兩種特別的屬性,一種叫做「數據屬性」(Data properties),另一種叫做「訪問器屬性」(Accessor properties)。
這兩種屬性讓我們在處理物件和數據時可以有更多的靈活性和選擇。
數據屬性
數據屬性直接關聯到物件的值。當我們談論在物件上設置和獲取值時,我們通常指的是操作數據屬性。
舉例來說,當我們這樣定義一個物件時:
let person = {
name: '徐培鈞'
}
我們創建了一個名為 name 的數據屬性,它包含了字符串值 “John”。
訪問器屬性
訪問器屬性則不直接包含一個值,而是包含一對 getter 和 setter 函數。
當訪問一個訪問器屬性時,會調用其 getter 函數來返回值;當對其賦值時,會調用其 setter 函數處理這個值。
let person = {
// 這是一個數據屬性
name: '徐培鈞',
// 這是一個訪問器屬性
get name() {
return this._name;
}
}
什麼是 Getter
Getter 是一種讓我們可以「取得」物件屬性值的方法。
當我們從物件獲取某個屬性的值時,實際上是在執行 getter 函數。這允許我們在返回屬性值之前進行額外的處理或計算。
例如,如果我們想要在獲取某個人的全名時自動將名和姓合併,我們可以這樣定義 getter:
let person = {
firstName: "徐",
lastName: "培鈞",
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
console.log(person.fullName);
// 回傳:
// 徐培鈞
什麼是 Setter
Setter 允許我們在「賦值」物件屬性值時,執行一些自定義操作。
這意味著當我們嘗試設置某個屬性的值時,實際上是在調用 setter 函數。
比如,如果我們希望能夠一次性重新設定某人的姓氏 + 名字,並透過空格作為判斷標準,我們可以使用 setter 進行以下設定:
let person = {
firstName: "徐",
lastName: "培鈞",
set fullName(name) {
[this.firstName, this.lastName] = name.split(" ");
}
};
person.fullName = "王 小名";
console.log(person.firstName);
// 回傳:
// 王
console.log(person.lastName);
// 回傳:
// 小名
當你看到這段代碼時,可能會好奇為什麼設定 person.fullName = “王 小名”; 能夠使 setter 函數運作,看起來好像我們沒有直接呼叫方法一樣。
其實,當我們這樣賦值的時候,背後其實是用了一種特別的設計,叫做「訪問器屬性」。
這裡的 fullName 不是一個普通的屬性,它是個特殊的存在,有點像是個隱形的門,當你試圖給它賦值(也就是把名字放進去)的時候,它會自動觸發背後的一段代碼(這就是我們的 set fullName 那段)。
因為當你進行 person.fullName = “王 小名”; 這個動作時,你其實是在告訴程式:「嘿,我要設定 fullName 了」。
程式看到這個動作後,就會說:「好的,我知道你要設定 fullName,你給我的值是什麼?」
然後你給它的 “王 小名” 就自動被當作參數送進去了。
通常我們呼叫一個方法時,確實是像 methodName(value) 這樣子。
但在 JavaScript 裡,設計了一種訪問器特殊的方式讓我們操作物件的屬性,就像在操作普通屬性一樣簡單。
當你設定一個有 setter 的屬性時,就像是在用這種特別簡單的方式呼叫方法,只是看起來你只是在賦值而已。
這讓代碼讀起來更自然,也讓我們可以在設定屬性值時做一些額外的操作,比如我們的範例裡面把全名分開成姓和名。
所以,這種看起來像是直接賦值的動作其實背後有很多事情在發生,這就是 JavaScript 讓我們用更直觀的方式來控制物件屬性的巧妙之處。
Setter 和 Getter 的優點
在 JavaScript 裡面,setter 和 getter 不只是幫我們存取物件裡頭的東西那麼簡單;它們還能讓我們的代碼更加整齊、好讀、好維護,而且還能保護我們的資料不被亂改。
以下是使用它們的一些關鍵優點:
當然,讓我們透過一些簡單的示範代碼來解釋 setter 和 getter 的四大優點。
封裝性
封裝性,想像你有個寶箱,裡面放著一些秘密的東西。
你不希望任何人都能隨便打開這個箱子看到或改動裡面的東西,所以你給寶箱上了把鎖。
現在,如果有人想要放點什麼進去或從裡面取點什麼出來,他們必須要通過你給的鑰匙(也就是一些特定的方法或手段)才行。
在程式裡的「物件」也是類似的概念。一個物件會有一些內部的資料(像是寶箱裡的秘密東西),這些資料我們不希望被外界隨便訪問或修改。
所以,我們會用封裝性這個特性「上鎖」,隱藏這些內部資料。
然後,我們提供一些特定的方法(也就是「鑰匙」),讓外界在需要的時候可以通過這些方法來「開鎖」,也就是安全地讀取或更新這些資料,而不是直接去碰那些隱藏起來的內部狀態。
這麼做的好處是可以確保物件的資料安全,避免被不當操作,也讓物件的使用更加安全、清晰和方便。
class Person {
constructor(name) {
this._name = name; // 使用 _ 表示這是一個私有屬性
}
// 提供 getter 方法來訪問名字
get name() {
return this._name;
}
// 提供 setter 方法來安全設置名字
set name(value) {
this._name = value.trim(); // 移除名字兩側的空格
}
}
let person = new Person(" John ");
console.log(person.name); // John,自動移除了空格
延伸閱讀:類別(class)是什麼?|JavaScript 初學者筆記(4)
可讀性和可維護性
通過明確的方法(getter 和 setter)來訪問屬性,代碼變得更易於讀取和維護。
class Circle {
constructor(radius) {
this._radius = radius;
}
// 使用 getter 來提供半徑
get radius() {
return this._radius;
}
// 使用 setter 來設置半徑,同時確保半徑永遠是正數
set radius(value) {
this._radius = Math.abs(value);
}
// 額外的方法來顯示圓的面積
get area() {
return Math.PI * this._radius * this._radius;
}
}
let circle = new Circle(-5);
console.log(circle.radius); // 5,自動轉為正數
console.log(circle.area); // 顯示圓的面積
驗證和控制
在設置屬性值時可以添加額外的驗證邏輯,確保數據的正確性。
class User {
constructor(email) {
this._email = email;
}
get email() {
return this._email;
}
set email(value) {
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
this._email = value;
} else {
console.log("Invalid email address.");
}
}
}
let user = new User("example@example.com");
console.log(user.email);
// 回傳:
// example@example.com
user.email = "wrong-email";
// 回傳:
// Invalid email address.
延伸閱讀:深入探索 JavaScript 中的 RegExp:創建動態規則運算式
實現計算屬性
計算屬性允許你在訪問屬性時動態計算值。
class Rectangle {
constructor(width, height) {
this._width = width;
this._height = height;
}
get area() {
return this._width * this._height;
}
}
let rectangle = new Rectangle(5, 10);
console.log(rectangle.area); // 50,動態計算面積
結論
本文介紹了 JavaScript 中 setter 和 getter 的概念,以及它們與數據屬性和訪問器屬性的關係。
這些特性為開發者提供了強大的工具來增強代碼的封裝性、靈活性和安全性,同時也使代碼變得更加可讀和易於維護。