跳至主要内容

[JS] setter & getter

TL;DR

介紹setter以及getter。

參考資料

相關連結


setter

語法

{set prop(val) {...}}
{set [expression](val) {...}} // ES6新增可以使用計算屬性名(computed property name)表達式,綁定到給定函式
tip

需要注意,當我們使用setter的時候,並不是使用parameter去傳入參數,而是使用等號(=)賦值!

基礎範例

透過範例了解如何使用setter:

const language = {
set current(name) {
this.log.push(name);
},
log: [],
};

language.current = 'EN';
language.current = 'FA';

console.log(language.log); // ["EN", "FA"]

計算屬性名範例

const expr = "foo";

const obj = {
baz: "bar",
set [expr](v) {
this.baz = v;
},
};

console.log(obj.baz); // "bar"
obj.foo = "baz"; // 跑 setter
console.log(obj.baz); // "baz"

getter

語法

{get prop() { ... } }
{get [expression]() { ... } }

範例

直接透過範例,來了解如何使用Getter。

const obj = {
log: ['a', 'b', 'c'],
get latest() {
return this.log[this.log.length - 1];
},
};

console.log(obj.latest); // c

計算機屬性名範例

var expr = "foo";

var obj = {
get [expr]() {
return "bar";
},
};

console.log(obj.foo); // "bar"

綜合範例

定義setter:

const myWallet={
deposit:100,
set save(price){
this.deposit = this.deposit + price / 2; // 每次只會存入一半
}
}

myWallet.save=50;
// 使用"等號"觸發setter,存入一半的金額(i.e,25元)

console.log(myWallet.deposit); // 125

定義getter

const myWallet={
deposit:100,
set save(price){
this.deposit = this.deposit + price / 2; // 每次只會存入一半
},
get halfDeposit(){
return this.deposit / 2; // 取出的時候只會取得一半
}
}

myWallet.save=50; // 使用"等號"觸發setter,存入一半的金額(i.e,25元)

console.log(myWallet,myWallet.halfDeposit);
// {deposit: 125} 62.5

上方範例首先先透過*setter(save)*將25存入deposit屬性內

接著印出myWallet物件,可以看到裡面的存款(deposit)已經將預設值100加上剛剛的25了,並且透過*getter(halfDeposit)*取出存款的一半出來,所以根據return的內容會是62.5

getter的(...)

caution

如果今天將setter(save)執行順序往後調整如下

const myWallet={
deposit:100,
set save(price){
this.deposit = this.deposit + price / 2; // 每次只會存入一半
},
get halfDeposit(){
return this.deposit / 2; // 取出的時候只會取得一半
}
}

console.log(myWallet,myWallet.halfDeposit);
// {deposit: 100} 50

myWallet.save=50;

看起來很合理,目前myWallet物件內的存款金額是100,並且透過getter取出一半所以是50。

但是,如果我們將myWallet物件內容展開,可以發現裡面其實存在一個halfDeposit的屬性,內容是(...)

這個屬性是,當我們只要有使用到getter的時候就會自動加上的(setter不會有此行為)

執行結果圖示

上述程式碼片段執行結果

我們可以點擊(...)展開內容,以這個範例來說,會出現62.5。

執行結果圖示

Image

也就是說,該getter屬性會在點選的當下才計算

在點選getter屬性的時候因為已經將myWallet.save=50執行完畢了,所以才會顯示62.5

測試:使用setTimeout設定十秒後才透過setter存入金額,會不會影響點選getter屬性的值

結論:會。

const myWallet={
deposit:100,
set save(price){
this.deposit = this.deposit + price / 2;
},
get halfDeposit(){
return this.deposit / 2;
}
}

setTimeout(() => {
myWallet.save=1000;
console.log('10秒後存入1000元');
}, 10000);

console.log(myWallet) // 測試馬上打開
console.log(myWallet) // 測試十秒後打開

等到執行setter之後才點擊的結果,會與執行setter之前的不同 等到時間到才開啟的halfDeposit:(...)會顯示為300(100+1000/2=600,透過getter取出時只取出一半,所以是300)

defineProperty設定getter及setter

範例

我們也可以直接透過defineProperty設定getter以及setter。

如果還不熟悉definePrpoerty,可以參考前面幾篇的內容。

var foo = { a: 0 };

Object.defineProperty(foo, "bar", {
set: function (x) {
this.a = x / 2;
},
get: function() {
return this.a+1;
}
});

foo.bar = 10; // Runs the setter, which assigns 10 / 2 (5) to the 'a' property
console.log(foo.a); // 5

console.log(foo.bar); // 6

屬性特徵差異

我們透過defineProperty定義getter以及setter,與直接在物件實字定義時就寫入set,get的方式,兩者之間最大的差異在於他們的enumerable以及configurable會不同。

透過以下範例來了解:

const foo={
deposit:10,
set saveDeposit(price){
this.deposit=this.deposit+price/2;
},
get getDeposit(){
return this.deposit / 2;
}
}

const bar={
list:[],
}
Object.defineProperty(bar,"friends",{
set:function(friend){
this.list.push(friend);
},
get:function(){
return this.list;
}
})

console.log(Object.getOwnPropertyDescriptor(foo,'getDeposit'));
// {set: undefined, enumerable: true, configurable: true, get: ƒ}
console.log(Object.getOwnPropertyDescriptor(foo,'saveDeposit'));
// {get: undefined, enumerable: true, configurable: true, set: ƒ}

console.log(Object.getOwnPropertyDescriptor(bar,'friends'));
// {enumerable: false, configurable: false, get: ƒ, set: ƒ}

可以發現直接在物件創建階段就定義好的getter及setter的enumerable以及configurable都會是true

而直接利用defineProperty設定的getter及setter,其在enumerableconfigurable特徵則都會是false

另外,也會發現getter和setter的屬性特徵物件並沒有value以及writable的屬性。

info

如果希望透過defineProperty設定的getter及setter的屬性特徵與直接在物件初始化定義的特徵相同,我們可以調整如下:

const bar={
list:[],
}
Object.defineProperty(bar,"friends",{
confiturable:true,
enumerable:true,
set:function(friend){
this.list.push(friend);
},
get:function(){
return this.list;
}
})

console.log(Object.getOwnPropertyDescriptor(bar,'friends'));
// {enumerable: true, configurable: false, get: ƒ, set: ƒ}

delete移除getter及setter

以以下範例來說:

var foo = { a: 0 };

Object.defineProperty(foo, "bar", {
configurable:true, // 記得修改成true,才可以使用delete運算子刪除
set: function (x) {
this.a = x / 2;
},
get: function() {
return this.a+1;
}
});

console.log(foo); // 觀察目前的foo物件存在我們所定義的"bar" getter & setter (同名)

delete foo.bar // 使用delete運算子刪除,因為getter & setter 同名,所以兩個都會被移除

console.log(foo);

console.log(foo.bar);
foo.bar=100;

刪除前:
刪除前

刪除後:
刪除後