隨手扎
7天搞懂JS進階議題(day07)-Symbol & Proxy: 以前沒有的
本系列文章討論JS 物件導向設計相關的特性。 不含CSS,不含HTML!
建議先有些JS基礎再繼續閱讀。
你也可以看看從零開始遲來的Web開發筆記
雖然是「7天寫作松」挑戰,但同樣可以視為系列後續文章
No CSS! No HTML! No Browser!
Just need programming language
最後一天,來看兩個特別的類別–Symbol
和Proxy
。
以前的物件(object)
key只能是字串
在以前,物件的key一定要是基礎字串,不過因為JS語法糖的關係可以不用加引號:
var obj = {
"name": "World",
}
// 等價於
var obj = {
name: "World",
}
如果不是呢?
數字?不,一樣會是字串
var obj = {
1: "World",
}
console.log(obj[1]); // 喔~好像有那麼一回事喔?
console.log(obj["1"]); // 不!字串也取到同一個
// console.log(obj.1); // 而且不能用點(`.`)取值
不好意思,數字會自動轉成字串。
陣列?不,一樣會是字串
在ES6可以透過方括號([]
)放入運算值:
var obj = {
[1+3]: "World",
[.1+.2]: "Oh Oh",
}
console.log(obj[4]); // 可以這麼取
console.log(obj[.3]); // 不過要小心...`.3` 不等於 `.1+.2`
Ok,來試試看陣列:
var arr = [1, 2, 3, 4];
var obj = {
[arr]: "World",
}
console.log(obj[arr]); // 喔~好像有那麼一回事喔?
console.log(obj["1,2,3,4"]); // 不!字串也取到同一個
console.log(obj[arr.toString()]); // 等價於上面
// 如果arr改變就GG了
arr.push(5);
console.log(obj[arr]); // undefined
console.log(obj[arr.toString()]); // undefined
不好意思,數字會自動轉成字串。
物件?不,一樣會是字串
var key_obj = {};
var other_key_obj = {other:1};
var obj = {
[key_obj]: "World",
}
console.log(obj[key_obj]); // 喔~好像有那麼一回事喔?
console.log(obj[other_key_obj]); // 不!字串也取到同一個
console.log(obj['[object Object]']); // 等價於第一個
console.log(obj.toString()); // 等價於上面
不好意思,數字會自動轉成字串。
但ES6可以用Symbol
還記得第三天出現過getOwnPropertySymbols
嗎?
// 第三天類似內容
var obj = {
name: "World",
hello(){console.log(`Hello, ${this.name}`)},
}
Object.getOwnPropertyNames(obj) // => [ 'name', 'hello' ]
Object.getOwnPropertySymbols(obj) // => []
console.dir(obj) // => { name: 'World', hello: [Function: hello] }
Symbol
的欄位,只能用當時的Symbol
取,在建立一個看似相同的也不行。
var sym1 = Symbol("Hello");
var sym2 = Symbol("Hello");
var obj = {
[sym1]: "Hello",
[sym2]: "World",
};
console.log(obj[sym1]); // Hello
console.log(obj[sym2]); // World
console.log(obj["sym1"]); // undefined
console.log(obj["sym2"]); // undefined
Object.getOwnPropertyNames(obj) // => []
Object.getOwnPropertySymbols(obj) // => [ Symbol(Hello), Symbol(Hello) ]
Symbol
然看看Symbol
又什麼特別的。
不能用new Operator建立
var sym1 = new Symbol("Hello");
/*
Thrown:
TypeError: Symbol is not a constructor
at new Symbol (<anonymous>)
*/
是symbol又不是Symbol…
意外發現的
sym1
是"symbol"
類型,卻不是Symbol
的實例。儘管他符合instanceof
檢驗的規則。
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
var sym1 = Symbol("Hello");
console.log(typeof sym1); // "symbol"
Object.is(sym1.__proto__, Symbol.prototype); // true
sym1 instanceof Symbol // false, 儘管上面是true,但用`instanceof`檢測卻是false
每次建立都不相同,就算給的是相同參數
var sym1 = Symbol("Hello");
var sym2 = Symbol("Hello");
console.log(sym1 == sym2); // false
console.log(sym1 === sym2); // false
console.log(Object.is(sym1, sym2)); // false
透過Symbol
建立的物件,只有自己相同,跟其他都不同。不過你可以設置參考:
console.log(sym1 == sym1); // true
console.log(sym1 === sym1); // true
console.log(Object.is(sym1, sym1)); // true
var ref_sym1 = sym1;
console.log(sym1 == ref_sym1); // true
console.log(sym1 === ref_sym1); // true
console.log(Object.is(sym1, ref_sym1)); // true
和基本型別一樣,無法新增屬性
sym1.a = 1;
console.log(sym1.a); // undefined
小節
好了,以上差不多就是Symbol
的內容。起初最讓我困惑的是是symbol又不是Symbol。不過其實Symbol
物件跟基礎類別一樣,不在物件的原形鏈上,所以也不能用defineProperty
新增屬性。
sym1 instanceof Object; // false
Object.defineProperty(sym1, "a", {value:1, configurable: true})
/*
Thrown:
TypeError: Object.defineProperty called on non-object
at Function.defineProperty (<anonymous>)
*/
Proxy
語法
以下語法引用自MDN。
let p = new Proxy(target, handler);
參數
參數 | 說明 |
---|---|
target | 用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 |
handler | 一个对象,其属性是当执行一个操作时定义代理的行为的函数。 |
示例
let handler = {
get: function(target, name){
return name in target ? target[name] : "Default";
}
};
var proxy_obj = new Proxy(obj, handler);
obj.hello = function(){
console.log(`Hello, ${this.name}`);
}
obj.hello(); // Hello, undefined
proxy_obj.hello(); // Hello, Default
proxy_obj.name = "World";
obj.hello(); // Hello, World
proxy_obj.hello(); // Hello, World
上例中,對proxy_obj
操作時,會去檢查obj
是否有需要的欄位,如果沒有就回傳"Default"
。obj
並沒有name
的欄位,所以透過obj
和透過proxy_obj
呼叫hello()
的結果並不相同。
隨後,透過proxy_obj
新增了name
欄位,但因為沒有定義set
的代理,所以也就更新到了obj
,所以透過obj
和透過proxy_obj
呼叫hello()
有相同結果。
proxy 不是Proxy
/ 沒有Proxy.prototype
在我嘗試的兩個Node.js版本–v10.15.3和v12.13.0有點不太一樣。我認為後者更嚴謹合理。不過先看看v10.15.3的版本
// at node.js v10.15.3
proxy_obj instanceof Proxy; // false
Proxy.prototype; // null
然後看看v12.13.0,以及Firefox Browser v73.0裡嘗試的結果:
// at node.js v12.13.0
Proxy.prototype; // undefined
proxy_obj instanceof Proxy; // 報錯!
/*
Thrown:
TypeError: Function has non-object prototype 'undefined' in instanceof check
at Function.[Symbol.hasInstance] (<anonymous>)
*/
Proxy
沒有prototype
,當然不能用instanceof
檢驗。
不能代理基礎型別
new Proxy(1, handler);
/*
Thrown:
TypeError: Cannot create proxy with a non-object as target or handler
*/
遺珠之憾
本系列文章至此告一段落,但還有一些應該知道,卻沒提及的東西。其中一個原因是接些東西離物件有點遠,此外也需要一些篇幅。最後僅稍微題一下。
- Promise
| 排程事件處理,並安排成功與失敗的後續。 - async/await | 非同步的語法糖。