摘要:拿來做些什么因為在使用了后,對象的行為基本上都是可控的,所以我們能拿來做一些之前實現起來比較復雜的事情。如果沒有使用關鍵字來調用的話,對象會直接拋出異常,而中的構造函數指向則會變為調用函數時的作用域。
Proxy是什么
首先,我們要清楚,Proxy是什么意思,這個單詞翻譯過來,就是 代理。
可以理解為,有一個很火的明星,開通了一個微博賬號,這個賬號非?;钴S,回復粉絲、到處點贊之類的,但可能并不是真的由本人在維護的。
而是在背后有一個其他人 or 團隊來運營,我們就可以稱他們為代理人,因為他們發表的微博就代表了明星本人的意思。
P.S. 強行舉例子,因為本人不追星,只是猜測可能會有這樣的運營團隊
這個代入到JavaScript當中來,就可以理解為對對象或者函數的代理操作。
JavaScript中的ProxyProxy是ES6中提供的新的API,可以用來定義對象各種基本操作的自定義行為
(在文檔中被稱為traps,我覺得可以理解為一個針對對象各種行為的鉤子)
拿它可以做很多有意思的事情,在我們需要對一些對象的行為進行控制時將變得非常有效。
創建一個Proxy的實例需要傳入兩個參數
target 要被代理的對象,可以是一個object或者function
handlers對該代理對象的各種操作行為處理
let target = {} let handlers = {} // do nothing let proxy = new Proxy(target, handlers) proxy.a = 123 console.log(target.a) // 123
在第二個參數為空對象的情況下,基本可以理解為是對第一個參數做的一次淺拷貝
(Proxy必須是淺拷貝,如果是深拷貝則會失去了代理的意義)
就像上邊的示例代碼一樣,如果沒有定義對應的trap,則不會起任何作用,相當于直接操作了target。
當我們寫了某個trap以后,在做對應的動作時,就會觸發我們的回調函數,由我們來控制被代理對象的行為。
最常用的兩個trap應該就是get和set了。
早年JavaScript有著在定義對象時針對某個屬性進行設置getter、setter:
let obj = { _age: 18, get age () { return `I"m ${this._age} years old` }, set age (val) { this._age = Number(val) } } console.log(obj.age) // I"m 18 years old obj.age = 19 console.log(obj.age) // I"m 19 years old
就像這段代碼描述的一樣,我們設置了一個屬性_age,然后又設置了一個get age和set age。
然后我們可以直接調用obj.age來獲取一個返回值,也可以對其進行賦值。
這么做有幾個缺點:
針對每一個要代理的屬性都要編寫對應的getter、setter。
必須還要存在一個存儲真實值的key(如果我們直接在getter里邊調用this.age則會出現堆棧溢出的情況,因為無論何時調用this.age進行取值都會觸發getter)。
Proxy很好的解決了這兩個問題:
let target = { age: 18, name: "Niko Bellic" } let handlers = { get (target, property) { return `${property}: ${target[property]}` }, set (target, property, value) { target[property] = value } } let proxy = new Proxy(target, handlers) proxy.age = 19 console.log(target.age, proxy.age) // 19, age : 19 console.log(target.name, proxy.name) // Niko Bellic, name: Niko Bellic
我們通過創建get、set兩個trap來統一管理所有的操作,可以看到,在修改proxy的同時,target的內容也被修改,而且我們對proxy的行為進行了一些特殊的處理。
而且我們無需額外的用一個key來存儲真實的值,因為我們在trap內部操作的是target對象,而不是proxy對象。
因為在使用了Proxy后,對象的行為基本上都是可控的,所以我們能拿來做一些之前實現起來比較復雜的事情。
在下邊列出了幾個簡單的適用場景。
在一些層級比較深的對象屬性獲取中,如何處理undefined一直是一個痛苦的過程,如果我們用Proxy可以很好的兼容這種情況。
(() => { let target = {} let handlers = { get: (target, property) => { target[property] = (property in target) ? target[property] : {} if (typeof target[property] === "object") { return new Proxy(target[property], handlers) } return target[property] } } let proxy = new Proxy(target, handlers) console.log("z" in proxy.x.y) // false (其實這一步已經針對`target`創建了一個x.y的屬性) proxy.x.y.z = "hello" console.log("z" in proxy.x.y) // true console.log(target.x.y.z) // hello })()
我們代理了get,并在里邊進行邏輯處理,如果我們要進行get的值來自一個不存在的key,則我們會在target中創建對應個這個key,然后返回一個針對這個key的代理對象。
這樣就能夠保證我們的取值操作一定不會拋出can not get xxx from undefined
但是這會有一個小缺點,就是如果你確實要判斷這個key是否存在只能夠通過in操作符來判斷,而不能夠直接通過get來判斷。
如果我們提供了一個Class對象給其他人,或者說一個ES5版本的構造函數。
如果沒有使用new關鍵字來調用的話,Class對象會直接拋出異常,而ES5中的構造函數this指向則會變為調用函數時的作用域。
我們可以使用apply這個trap來兼容這種情況:
class Test { constructor (a, b) { console.log("constructor", a, b) } } // Test(1, 2) // throw an error let proxyClass = new Proxy(Test, { apply (target, thisArg, argumentsList) { // 如果想要禁止使用非new的方式來調用函數,直接拋出異常即可 // throw new Error(`Function ${target.name} cannot be invoked without "new"`) return new (target.bind(thisArg, ...argumentsList))() } }) proxyClass(1, 2) // constructor 1 2
我們使用了apply來代理一些行為,在函數調用時會被觸發,因為我們明確的知道,代理的是一個Class或構造函數,所以我們直接在apply中使用new關鍵字來調用被代理的函數。
以及如果我們想要對函數進行限制,禁止使用new關鍵字來調用,可以用另一個trap:construct
function add (a, b) { return a + b } let proxy = new Proxy(add, { construct (target, argumentsList, newTarget) { throw new Error(`Function ${target.name} cannot be invoked with "new"`) } }) proxy(1, 2) // 3 new proxy(1, 2) // throw an error用Proxy來包裝fetch
在前端發送請求,我們現在經常用到的應該就是fetch了,一個原生提供的API。
我們可以用Proxy來包裝它,使其變得更易用。
let handlers = { get (target, property) { if (!target.init) { // 初始化對象 ["GET", "POST"].forEach(method => { target[method] = (url, params = {}) => { return fetch(url, { headers: { "content-type": "application/json" }, mode: "cors", credentials: "same-origin", method, ...params }).then(response => response.json()) } }) } return target[property] } } let API = new Proxy({}, handlers) await API.GET("XXX") await API.POST("XXX", { body: JSON.stringify({name: 1}) })
對GET、POST進行了一層封裝,可以直接通過.GET這種方式來調用,并設置一些通用的參數。
實現一個簡易的斷言工具寫過測試的各位童鞋,應該都會知道斷言這個東西
console.assert就是一個斷言工具,接受兩個參數,如果第一個為false,則會將第二個參數作為Error message拋出。
我們可以使用Proxy來做一個直接賦值就能實現斷言的工具。
let assert = new Proxy({}, { set (target, message, value) { if (!value) console.error(message) } }) assert["Isn"t true"] = false // Error: Isn"t true assert["Less than 18"] = 18 >= 19 // Error: Less than 18統計函數調用次數
在做服務端時,我們可以用Proxy代理一些函數,來統計一段時間內調用的次數。
在后期做性能分析時可能會能夠用上:
function orginFunction () {} let proxyFunction = new Proxy(orginFunction, { apply (target, thisArg. argumentsList) { log(XXX) return target.apply(thisArg, argumentsList) } })全部的traps
這里列出了handlers所有可以定義的行為 (traps):
具體的可以查看MDN-Proxy
里邊同樣有一些例子
traps | description |
---|---|
get | 獲取某個key值 |
set | 設置某個key值 |
has | 使用in操作符判斷某個key是否存在 |
apply | 函數調用,僅在代理對象為function時有效 |
ownKeys | 獲取目標對象所有的key |
construct | 函數通過實例化調用,僅在代理對象為function時有效 |
isExtensible | 判斷對象是否可擴展,Object.isExtensible的代理 |
deleteProperty | 刪除一個property |
defineProperty | 定義一個新的property |
getPrototypeOf | 獲取原型對象 |
setPrototypeOf | 設置原型對象 |
preventExtensions | 設置對象為不可擴展 |
getOwnPropertyDescriptor | 獲取一個自有屬性 (不會去原型鏈查找) 的屬性描述 |
Magic Methods in JavaScript? Meet Proxy!
How to use JavaScript Proxies for Fun and Profit
MDN-Proxy
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95239.html
摘要:不僅如此,倒霉的不只是文科生,根據劍橋大學的數據,目前熱門的工程師居然有的幾率被淘汰,程序員也有接近的幾率被淘汰。 現如今人工智能已經在很多方面得到了應用落地,通過手機移動端的一些功能和應用程序,我們已經能夠很直觀地感受到它對我們生活的影響。比如蘋果IphoneX的人臉識別功能,包括一些主流app有語音識別和語音對話的功能。 隨著AI時代的移動技術革新大會開幕鄰近,APICloud創始...
摘要:年初,金山啟動私有云項目,該項目旨在為向金山提出了私有云網盤存儲需求的政府大型企業以及中型企業提供服務,項目組由金山云楊鋼牽頭組建。中文站對楊鋼進行了專訪,了解其私有云服務的技術組成和業務狀態。 2013年初,金山啟動私有云項目,該項目旨在為向金山提出了私有云網盤/存儲需求的政府、大型企業以及中型企業提供服務,項目組由金山云CTO楊鋼牽頭組建。InfoQ中文站對楊鋼進行了專訪,了解其私有云服...
摘要:經歷了微博數據庫各個階段的架構改造,包括服務保障及體系建設微博多機房部署微博平臺化改造等項目。第二階段爆發階段微博上線之后,隨著用戶活躍度的增加,數據庫的壓力也與日俱增。 非商業轉載請注明作譯者、出處,并保留本文的原始鏈接:http://www.ituring.com.cn/article/211461 肖鵬,微博研發中心技術經理,主要負責微博數據庫(MySQL/Reids/HBase...
閱讀 1228·2021-09-26 09:46
閱讀 1582·2021-09-06 15:00
閱讀 713·2019-08-30 15:52
閱讀 1116·2019-08-29 13:10
閱讀 1276·2019-08-26 13:47
閱讀 1478·2019-08-26 13:35
閱讀 2027·2019-08-23 18:38
閱讀 721·2019-08-23 17:59