您好,登錄后才能下訂單哦!
小編給大家分享一下JavaScript中的Proxy是什么,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!
首先,我們要清楚,Proxy
是什么意思,這個(gè)單詞翻譯過來,就是 代理。
可以理解為,有一個(gè)很火的明星,開通了一個(gè)微博賬號(hào),這個(gè)賬號(hào)非?;钴S,回復(fù)粉絲、到處點(diǎn)贊之類的,但可能并不是真的由本人在維護(hù)的。
而是在背后有一個(gè)其他人 or 團(tuán)隊(duì)來運(yùn)營,我們就可以稱他們?yōu)榇砣?,因?yàn)樗麄儼l(fā)表的微博就代表了明星本人的意思。
P.S. 強(qiáng)行舉例子,因?yàn)楸救瞬蛔沸?,只是猜測可能會(huì)有這樣的運(yùn)營團(tuán)隊(duì)
這個(gè)代入到JavaScript
當(dāng)中來,就可以理解為對對象
或者函數(shù)
的代理操作。
Proxy是ES6中提供的新的API,可以用來定義對象各種基本操作的自定義行為 (在文檔中被稱為traps,我覺得可以理解為一個(gè)針對對象各種行為的鉤子),拿它可以做很多有意思的事情,在我們需要對一些對象的行為進(jìn)行控制時(shí)將變得非常有效。
創(chuàng)建一個(gè)Proxy
的實(shí)例需要傳入兩個(gè)參數(shù)
target
要被代理的對象,可以是一個(gè)object
或者function
handlers
對該代理對象的各種操作行為處理
let target = {} let handlers = {} // do nothing let proxy = new Proxy(target, handlers) proxy.a = 123 console.log(target.a) // 123
在第二個(gè)參數(shù)為空對象的情況下,基本可以理解為是對第一個(gè)參數(shù)做的一次淺拷貝
(Proxy必須是淺拷貝,如果是深拷貝則會(huì)失去了代理的意義)
就像上邊的示例代碼一樣,如果沒有定義對應(yīng)的trap
,則不會(huì)起任何作用,相當(dāng)于直接操作了target
。
當(dāng)我們寫了某個(gè)trap
以后,在做對應(yīng)的動(dòng)作時(shí),就會(huì)觸發(fā)我們的回調(diào)函數(shù),由我們來控制被代理對象的行為。
最常用的兩個(gè)trap
應(yīng)該就是get
和set
了。
早年JavaScript
有著在定義對象時(shí)針對某個(gè)屬性進(jìn)行設(shè)置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
就像這段代碼描述的一樣,我們設(shè)置了一個(gè)屬性_age
,然后又設(shè)置了一個(gè)get age
和set age
。
然后我們可以直接調(diào)用obj.age
來獲取一個(gè)返回值,也可以對其進(jìn)行賦值。
這么做有幾個(gè)缺點(diǎn):
針對每一個(gè)要代理的屬性都要編寫對應(yīng)的getter
、setter
。
必須還要存在一個(gè)存儲(chǔ)真實(shí)值的key
(如果我們直接在getter里邊調(diào)用this.age則會(huì)出現(xiàn)堆棧溢出的情況,因?yàn)闊o論何時(shí)調(diào)用this.age進(jìn)行取值都會(huì)觸發(fā)getter)。
Proxy
很好的解決了這兩個(gè)問題:
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
我們通過創(chuàng)建get
、set
兩個(gè)trap
來統(tǒng)一管理所有的操作,可以看到,在修改proxy
的同時(shí),target
的內(nèi)容也被修改,而且我們對proxy
的行為進(jìn)行了一些特殊的處理。
而且我們無需額外的用一個(gè)key
來存儲(chǔ)真實(shí)的值,因?yàn)槲覀冊?code>trap內(nèi)部操作的是target
對象,而不是proxy
對象。
因?yàn)樵谑褂昧?code>Proxy后,對象的行為基本上都是可控的,所以我們能拿來做一些之前實(shí)現(xiàn)起來比較復(fù)雜的事情。
在下邊列出了幾個(gè)簡單的適用場景。
在一些層級(jí)比較深的對象屬性獲取中,如何處理undefined
一直是一個(gè)痛苦的過程,如果我們用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 (其實(shí)這一步已經(jīng)針對`target`創(chuàng)建了一個(gè)x.y的屬性) proxy.x.y.z = 'hello' console.log('z' in proxy.x.y) // true console.log(target.x.y.z) // hello })()
我們代理了get
,并在里邊進(jìn)行邏輯處理,如果我們要進(jìn)行get
的值來自一個(gè)不存在的key
,則我們會(huì)在target
中創(chuàng)建對應(yīng)個(gè)這個(gè)key
,然后返回一個(gè)針對這個(gè)key
的代理對象。
這樣就能夠保證我們的取值操作一定不會(huì)拋出can not get xxx from undefined
但是這會(huì)有一個(gè)小缺點(diǎn),就是如果你確實(shí)要判斷這個(gè)key
是否存在只能夠通過in
操作符來判斷,而不能夠直接通過get
來判斷。
如果我們提供了一個(gè)Class
對象給其他人,或者說一個(gè)ES5
版本的構(gòu)造函數(shù)。
如果沒有使用new
關(guān)鍵字來調(diào)用的話,Class
對象會(huì)直接拋出異常,而ES5
中的構(gòu)造函數(shù)this
指向則會(huì)變?yōu)檎{(diào)用函數(shù)時(shí)的作用域。
我們可以使用apply
這個(gè)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的方式來調(diào)用函數(shù),直接拋出異常即可 // throw new Error(`Function ${target.name} cannot be invoked without 'new'`) return new (target.bind(thisArg, ...argumentsList))() } }) proxyClass(1, 2) // constructor 1 2
我們使用了apply
來代理一些行為,在函數(shù)調(diào)用時(shí)會(huì)被觸發(fā),因?yàn)槲覀兠鞔_的知道,代理的是一個(gè)Class
或構(gòu)造函數(shù),所以我們直接在apply
中使用new
關(guān)鍵字來調(diào)用被代理的函數(shù)。
以及如果我們想要對函數(shù)進(jìn)行限制,禁止使用new
關(guān)鍵字來調(diào)用,可以用另一個(gè)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
在前端發(fā)送請求,我們現(xiàn)在經(jīng)常用到的應(yīng)該就是fetch
了,一個(gè)原生提供的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
進(jìn)行了一層封裝,可以直接通過.GET
這種方式來調(diào)用,并設(shè)置一些通用的參數(shù)。
寫過測試的各位童鞋,應(yīng)該都會(huì)知道斷言這個(gè)東西console.assert
就是一個(gè)斷言工具,接受兩個(gè)參數(shù),如果第一個(gè)為false
,則會(huì)將第二個(gè)參數(shù)作為Error message
拋出。
我們可以使用Proxy
來做一個(gè)直接賦值就能實(shí)現(xiàn)斷言的工具。
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
在做服務(wù)端時(shí),我們可以用Proxy
代理一些函數(shù),來統(tǒng)計(jì)一段時(shí)間內(nèi)調(diào)用的次數(shù)。
在后期做性能分析時(shí)可能會(huì)能夠用上:
function orginFunction () {} let proxyFunction = new Proxy(orginFunction, { apply (target, thisArg. argumentsList) { log(XXX) return target.apply(thisArg, argumentsList) } })
這里列出了handlers
所有可以定義的行為 (traps):
具體的可以查看MDN-Proxy
里邊同樣有一些例子
traps | description |
---|---|
get | 獲取某個(gè)key 值 |
set | 設(shè)置某個(gè)key 值 |
has | 使用in 操作符判斷某個(gè)key 是否存在 |
apply | 函數(shù)調(diào)用,僅在代理對象為function 時(shí)有效 |
ownKeys | 獲取目標(biāo)對象所有的key |
construct | 函數(shù)通過實(shí)例化調(diào)用,僅在代理對象為function 時(shí)有效 |
isExtensible | 判斷對象是否可擴(kuò)展,Object.isExtensible 的代理 |
deleteProperty | 刪除一個(gè)property |
defineProperty | 定義一個(gè)新的property |
getPrototypeOf | 獲取原型對象 |
setPrototypeOf | 設(shè)置原型對象 |
preventExtensions | 設(shè)置對象為不可擴(kuò)展 |
getOwnPropertyDescriptor | 獲取一個(gè)自有屬性 (不會(huì)去原型鏈查找) 的屬性描述 |
看完了這篇文章,相信你對JavaScript中的Proxy是什么有了一定的了解,想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。