[day 07] Symbol & Proxy: 以前沒有的


本文同步發表於隨性筆記

本系列文章討論JS 物件導向設計相關的特性。 不含CSS,不含HTML
建議先有些JS基礎再繼續閱讀。
你也可以看看從零開始遲來的Web開發筆記
雖然是「7天寫作松」挑戰,但同樣可以視為系列後續文章

No CSS! No HTML! No Browser!
Just need programming language


最後一天,來看兩個特別的類別--SymbolProxy

以前的物件(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
    | 非同步的語法糖。
#js #javascript #EMCAScript
寫了這麼久的JS,你還在物件之前的時代嗎?只有資料、函式可以用,破破的抽象化,不會難以維護?儘管JS起初並不以物件導向設計,但透過原形鏈設計,其仍然可以具有好維護的物件導向特色。本系列從最基礎的this,深入ES6之後的class。






Related Posts

實作簡單的REST API

實作簡單的REST API

46 年老技術與 Web 的新火花 - Actor Model in Web

46 年老技術與 Web 的新火花 - Actor Model in Web

Module 模組化概念

Module 模組化概念

Day05:從 class 看 bytecode

Day05:從 class 看 bytecode

# JavaScript 技能 : NPM 心得

# JavaScript 技能 : NPM 心得

Python Table Manner - 程式碼風格

Python Table Manner - 程式碼風格



Comments