Proxy
ES6 的新產物,人如其名,功用就是 proxy,是個 object 代理人,可以使用者要操作這個 object 時都要先經過這個代理人的手
const target = {
foo: 'bar'
}const handler = {
get(target, prop, receiver) {
return target[prop] + '!'
},
set(target, prop, val, receiver) {
return target[prop] = val + '?'
}
}const p = new Proxy(target, handler)console.log(p.foo) // 'bar!'
p.foo = 'bbb'
console.log(p.foo) // 'bbb?!'
如上,透過 p
這個 Proxy 依照 handler 的邏輯來操作 target 物件,除了 get
、set
外還有其他操作可以被代理:
handler.apply()
A trap for a function call.handler.construct()
A trap for thenew
operator.handler.defineProperty()
A trap forObject.defineProperty
.handler.deleteProperty()
A trap for thedelete
operator.handler.get()
A trap for getting property values.handler.getOwnPropertyDescriptor()
A trap forObject.getOwnPropertyDescriptor
.handler.getPrototypeOf()
A trap forObject.getPrototypeOf
.handler.has()
A trap for thein
operator.handler.isExtensible()
A trap forObject.isExtensible
.handler.ownKeys()
A trap forObject.getOwnPropertyNames
andObject.getOwnPropertySymbols
.handler.preventExtensions()
A trap forObject.preventExtensions
.handler.set()
A trap for setting property values.handler.setPrototypeOf()
A trap forObject.setPrototypeOf
.
更多細節請參考最下方文件連結
Reflect
講到 Proxy 就要知道他有個好朋友 Reflect,跟 Proxy 有著一樣的 handlers 以及相同的參數
Reflect.set
例如 set,會去幫你執行 set 這件事並回傳 true/false 表示是否成功
new Proxy(
{
foo: 'bar',
_foo: 'secret bar'
}, {
set(target, prop, val) {
if (prop.startsWith('_')) {
throw Error('fail to set item prefixed with _')
} return Reflect.set(...arguments)
}
}
)
但這不是本次重點就不多贅述,更多細節請參考最下方文件連結
localStorage
這是應該不用多談,就是個每次要讀寫 Object 都要先 JSON.stringify
、 JSON.parse
的麻煩傢伙
w/ Proxy
比起另外寫 function 去做轉換
const KEY = '__myStorage'const getValFromLocalStorage = () => {
return JSON.parse(localStorage[KEY] || '{}')
}const setValToLocalStorage = (val) => {
localStorage[KEY] = JSON.stringify(val)
}
也許可以換個想法,把 data 都存在 local object,也都針對這個 object 去操作,但在操作這個 object 的同時他會順便把資料存到 localStorage 中,也就變成:
const KEY = '__myStorage'
const initData = JSON.parse(localStorage[KEY] || '{}')const setDataToStorage = (data) => {
localStorage[KEY] = JSON.stringify(data)
}const createProxy = (initData) => new Proxy(initData, {
set(target, prop, val) {
if (Reflect.set(...arguments)) {
setDataToStorage(target)
return true
}
},
deleteProperty(target, prop) {
if (Reflect.deleteProperty(...arguments)) {
setDataToStorage(target)
return true
}
}
})let data = createProxy(initData)
接著就可以直接操作 data
物件,並同時會把 data 的值存到 localStorage 中
on localStorage change
但這邊會遇到一個問題,當使用者在多個分頁中操作時會發生 race condition,所以需要監聽 localStorage change event 來更新 data 這個 Proxy object,這邊可以透過 window.onstorage
來監聽
// 承上
window.addEventListener('storage', (e) => {
const { key, newValue } = e
if (key === KEY) {
data = createProxy(JSON.parse(newValue))
}
})
經過測試發現 storage event 觸發的時機為
- 用 browser devtool 操作 localStorage/sessionStorage
- 在不同分頁透過 JS 操作 localStorage/sessionStorage
另外要注意的是 storage event 好像
沒辦法分辨改變的是 localStorage 還是 sessionStorage,所以命名不要重複為上
實際應用
也就是開頭說到的小玩具
Demo GIF:
這是一個可以在 104 上把沒興趣的職缺過濾掉的小工具,而過濾掉的項目是直接記錄在 localStorage 中,並由上方 GIF 可見在 listing page 及 item page 中的操作會互相同步
而相關 Proxy 與 localStorage 交纏的部分可參考這裡: