本文同步發表於隨性筆記
本系列文章討論JS 物件導向設計相關的特性。 不含CSS,不含HTML!
建議先有些JS基礎再繼續閱讀。
你也可以看看從零開始遲來的Web開發筆記
雖然是「7天寫作松」挑戰,但同樣可以視為系列後續文章
No CSS! No HTML! No Browser!
Just need programming language
有了物件然後呢?
今天來說說關於成員(field/attribute/member)背後的屬性。
getter & setter
有一個家族-- 神崎家 ,生了一個小孩叫 アリア ,但生下後被 りこ 偽裝,被發現後重新命名為 アリア。
如有雷同存屬巧合
var 神崎家 = class {
constructor(name){
this.__name = `神崎・${name}`;
}
static born(name){
return new 神崎家(name)
}
set name(new_name){
this.__name = `神崎・${new_name}`;
}
get name(){
let first_name = this.__name.substr(0,2),
last_name = this.__name.substr(3, this.__name.length + 1);
return `${first_name}・H・${last_name}`
}
introduce(){
console.log(`Hi~ My name is ${this.name}`)
}
rename(new_name){
this.name = new_name;
return this.name;
}
}
var aria = 神崎家.born("アリア");
aria.introduce(); // => Hi~ My name is 神崎・H・アリア
aria.rename("りこ"); // => 神崎・H・りこ
aria.introduce(); // => Hi~ My name is 神崎・H・りこ
aria.name = "アリア";
aria.introduce(); // => Hi~ My name is 神崎・H・アリア
console.log(aria.name); // => 神崎・H・アリア
上例中, アリア 的家族名是 神崎 ,中間名是 H 。而且 アリア 一生下來就會自我介紹。
並且可以看到,有著get
描述器的函式,可以像是一般屬性一樣被存取;set
可以在賦予值時做加工。透過只給get
不給set
,還可以做到唯讀(read only)屬性。
Object.defineProperty(神崎家.prototype, "first_name", {
get(){return "神崎"},
configurable: true, // 僅為了之後測試方便
});
console.log(aria.first_name); // => 神崎
aria.first_name = "遠山";
console.log(aria.first_name); // => 神崎
aria.__proto__.first_name = "遠山";
console.log(aria.first_name); // => 神崎
沒人可以亂改家族名!
為什麼我要加在
神崎家.prototype
上呢?
不懂嗎?去看看關於Prototype Chain繼承
作業 - 更靈活的first_name你做得到嗎?
注意到一開始把"神崎"
寫死,成了一個魔術數字(magic number)了嗎?你有辦法把上面例子改的更靈活更有彈性吧?透過改寫和Object.defineProperty
加點工看看吧!部份程式碼可能如下:
很顯眼?
絕對不是因為我懶得刪程式碼
是要給你提示拉!
Object.defineProperty(神崎家.prototype, "first_name", {
get(){return this.__first_name||"神崎";},
set(v){this.__first_name = v;},
configurable: true, // 僅為了之後測試方便
});
accessor
除了getter
和setter
,還可以直接設定accessor
,不過關鍵字是value
:
Object.defineProperty(aria, "weapons" , {
value: ["雙槍", "雙劍"],
writable: true,
configurable: true, // 僅為了之後測試方便
});
console.log(aria.weapons); // => [ '雙槍', '雙劍' ]
其實也差不多就等於:
aria.rivals = ["峰 理子"]; // 對...就上面偽裝過 アリア 的人
defineProperty & getOwnPropertyDescriptor
上面例子應該夠你使用Object.defineProperty
了,還是不懂嗎?去參考一下更正式的文件吧!
這以小節主要用Object.getOwnPropertyDescriptor
1來看看上面設定的屬性。
首先看看一般直接設定的結果和accessor:
Object.getOwnPropertyDescriptor(aria, "weapons"); // => <BELOW>
// 不用註解有上色比較好看
{
value: [ '雙槍', '雙劍' ],
writable: true,
enumerable: false,
configurable: true
}
Object.getOwnPropertyDescriptor(aria, "rivals"); // => <BELOW>
{
value: [ '峰 理子' ],
writable: true,
enumerable: true,
configurable: true
}
可以看到最主要差別在enumerable
,表示是否能用for
去迭代。(點我看關於屬性描述器中的enumerable
補充)
再來看看getter
和setter
。就不多J4了。
Object.getOwnPropertyDescriptor(aria.__proto__, "first_name"); // => <BELOW>
{
get: [Function: get],
set: [Function: set],
enumerable: false,
configurable: true
}
2019/02/28 關於屬性描述器中的enumerable
補充
上面看到過關於enumerable
的內容。
Object.getOwnPropertyDescriptor(aria, "weapons"); // => <BELOW>
// 不用註解有上色比較好看
{
value: [ '雙槍', '雙劍' ],
writable: true,
enumerable: false,
configurable: true
}
Object.getOwnPropertyDescriptor(aria, "rivals"); // => <BELOW>
{
value: [ '峰 理子' ],
writable: true,
enumerable: true,
configurable: true
}
關於enumerable
的行為表現在物件aria
的迭代上(for-loop)。不過可以先來看看Object.keys()
的結果:
console.log(Object.keys(aria));
// => [ '__name', 'rivals' ]
可以看到並沒有weapons
,因為enumerable: false
。既然keys()
找不到,也大概可以理解為什麼迭代時找不到他了。來用for-in
試試:
for(key in aria){
console.log(`${key}: ${aria[key]}`);
}
/*Output:
__name: 神崎・アリア
rivals: 峰 理子
*/
不過有個例外:當key是symbol
時,不管如何都無法迭代:
Object.defineProperty(aria, Symbol("super_power") , {
value: ["超級推理"],
writable: true,
enumerable: true,
configurable: true, // 僅為了之後測試方便
});
s = Object.getOwnPropertySymbols(aria)
console.log(s); // => [ Symbol(super_power) ]
console.log(aria[s[0]]); // => [ '超級推理' ]
for(key in aria){
console.log(`${key}: ${aria[key]}`);
}
/*Output:
__name: 神崎・アリア
rivals: 峰 理子
*/
雖然可以找得到這個屬性,但是迭代裡不會出現,也不會出現在keys()
裡。