元編程是一種強大的技術,使你可以編寫能夠建立其餘程序的程序。ES6藉助代理和許多相似功能,使在JavaScript中利用元編程變得更加容易。ES6 Proxy(代理) 有助於從新定義對象的基本操做,從而爲各類可能性打開了大門。javascript
本指南能夠幫助您理解爲何ES6代理如此之好,尤爲是對於元編程而言:html
本教程主要針對有JavaScript經驗的開發人員,至少要熟悉ES6代理的概念。若是你已經對代理做爲一種設計模式有了牢固的理解,那麼這些知識應該能夠轉化爲現實。前端
閱讀本指南後,你應該可以:java
從根本上來講,代理是指某件事或某人成爲其餘事物的替代品,因此不論是什麼東西,都要通過替代品才能達到真正的交易。ES6代理的工做原理也是如此。es6
爲了有效地實現和使用ES6代理,你必須瞭解三個關鍵術語:編程
綜上所述,下面是最簡單的實現,若是使用ES6代理,對象中不存在給定的屬性,則能夠返回不一樣的內容。設計模式
const target = { someProp: 1 } const handler = { get: function(target, key) { return key in target ? target[key] : 'Doesn't exist!'; } } const proxy = new Proxy(target, handler); console.log(proxy.someProp) // 1 console.log(proxy.someOtherProp) // Doesn't exist!
ES6代理是一項強大的功能,可促進JavaScript中對象的虛擬化。api
因爲數據綁定的複雜性,它一般很難實現。ES6代理實現雙向數據綁定的應用能夠在JavaScript的MVC庫中看到,在這些庫中,當DOM發生變化時,對象會被修改。緩存
簡而言之,數據綁定是一種將多個數據源綁定在一塊兒以使其同步的技術。微信
假設存在一個名爲 username
的 <input>
。
<input type="text" id="username" />
假設你要使此輸入的值與對象的屬性保持同步。
const inputState = { id: 'username', value: '' }
當輸入的值發生變化時,經過監聽輸入的變化事件,而後更新 inputState
的值,修改 inputState
是至關容易的。然而,反過來,在 inputState
被修改時更新輸入,則至關困難。
ES6代理能夠在這種狀況下提供幫助。
const input = document.querySelector('#username') const handler = { set: function(target, key, value) { if (target.id && key === 'username') { target[key] = value; document.querySelector(`#${target.id}`) .value = value; return true } return false } } const proxy = new Proxy(inputState, handler) proxy.value = 'John Doe' console.log(proxy.value, input.value) // 雙方都將印有「 John Doe」
這樣,當 inputState
更改時,input
將反映已進行的更改。結合偵聽 change
事件,這將生成 input
和 inputState
的簡單雙向數據綁定。
雖然這是一個有效的用例,但一般不建議這樣作。之後再說。
緩存是一個古老的概念,它容許很是複雜和大型的應用程序保持相對的性能。緩存是存儲某些數據的過程,以便在請求時能夠更快地提供數據。緩存並不永久地存儲任何數據。緩存失效是保證緩存新鮮的過程。這是開發人員共同的苦惱。正如Phil Karlton所說:"計算機科學中只有兩件難事:緩存無效和給事物命名。"
ES6代理使緩存更加容易。例如,若是你要檢查對象中是否存在某些東西,它將首先檢查緩存並返回數據,或者若是不存在則進行其餘操做以獲取該數據。
假設你須要進行不少API調用才能獲取特定信息並對其進行處理。
const getScoreboad = (player) => { fetch('some-api-url') .then((scoreboard) => { // 用記分牌作點什麼 }) }
這就意味着,每當須要一個球員的記分牌時,就必須進行一次新的調用。相反,你能夠在第一次請求時緩存記分牌,隨後的請求能夠從緩存中獲取。
const cache = { 'John': ['55', '99'] } const handler = { get: function(target, player) { if(target[player] { return target[player] } else { fetch('some-api-url') .then(scoreboard => { target[player] = scoreboard return scoreboard }) } } } const proxy = new Proxy(cache, handler) // 訪問緩存並使用記分牌作一些事情
這樣,僅當緩存中不包含玩家的記分牌時,纔會進行API調用。
最簡單的用例是訪問控制,ES6代理的大部份內容都屬於訪問控制。
讓咱們探索使用E6代理的訪問控制的一些實際應用。
ES6代理最直觀的用例之一是驗證對象內部的內容,以確保對象中的數據儘量準確。例如,若是你想強制執行產品描述的最大字符數,能夠這樣作。
const productDescs = {} const handler = { set: function(target, key, value) { if(value.length > 150) { value = value.substring(0, 150) } target[key] = value } } const proxy = new Proxy(productDescs, handler)
如今,即便你添加的描述超過150個字符,也會被刪減並添加。
有時候你可能要確保不以任何方式修改對象,而且只能將其用於讀取目的。 JavaScript提供了 Object.freeze()
來執行此操做,可是使用代理時,該行爲更可自定義。
const importantData = { name: 'John Doe', age: 42 } const handler = { set: 'Read-Only', defineProperty: 'Read-Only', deleteProperty: 'Read-Only', preventExtensions: 'Read-Only', setPrototypeOf: 'Read-Only' } const proxy = new Proxy(importantData, handler)
如今,當你嘗試以任何方式更改對象時,你只會收到一個字符串,表示只讀。不然,你可能會引起錯誤以指示該對象是隻讀的。
JavaScript自己並無私有屬性,除了閉包。當 Symbol
數據類型被引入時,它被用來模仿私有屬性。但隨着Object.getOwnPropertySymbols
方法的引入,它被拋棄了。ES6代理並非一個完美的解決方案,但在緊要關頭它們能夠完成任務。
一種常見的約定是經過在名稱前加上下劃線來標識私有屬性,這個約定容許你使用ES6代理。
const object = { _privateProp: 42 } const handler = { has: function(target, key) { return !(key.startsWith('_') && key in target) }, get: function(target, key, receiver) { return key in receiver ? target[key] : undefined } } const proxy = new Proxy(object, handler) proxy._privateProp // undefined
添加 ownKeys
和 deleteProperty
會讓這個實現更接近於真正的私有屬性。而後,你仍然能夠在開發者控制檯中查看代理對象。若是你的用例與上面的實現一致,它仍然適用。
ES6代理並非性能密集型任務的理想選擇。這就是爲何進行必要的測試是相當重要的。代理能夠在任何預期對象的地方使用,代理只需幾行代碼就能提供複雜的功能,這使它成爲元編程的理想功能。
代理一般與另外一個稱爲Reflect的元編程功能一塊兒使用。
來源:https://blog.logrocket.com,做者:Eslam Hefnawy Follow,翻譯:公衆號《前端全棧開發者》
本文首發於微信公衆號《前端全棧開發者》,關注即送大禮包,準能爲你節省很多錢!