溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript的Proxy可以做哪些有意思的事兒

發(fā)布時間:2020-09-06 18:39:44 來源:腳本之家 閱讀:138 作者:賈順名 欄目:web開發(fā)

Proxy是什么

首先,我們要清楚,Proxy是什么意思,這個單詞翻譯過來,就是 代理。

可以理解為,有一個很火的明星,開通了一個微博賬號,這個賬號非?;钴S,回復(fù)粉絲、到處點(diǎn)贊之類的,但可能并不是真的由本人在維護(hù)的。

而是在背后有一個其他人 or 團(tuán)隊(duì)來運(yùn)營,我們就可以稱他們?yōu)榇砣?,因?yàn)樗麄儼l(fā)表的微博就代表了明星本人的意思。

P.S. 強(qiáng)行舉例子,因?yàn)楸救瞬蛔沸?,只是猜測可能會有這樣的運(yùn)營團(tuán)隊(duì)

這個代入到JavaScript當(dāng)中來,就可以理解為對對象或者函數(shù)的代理操作。

JavaScript中的Proxy

Proxy是ES6中提供的新的API,可以用來定義對象各種基本操作的自定義行為 (在文檔中被稱為traps,我覺得可以理解為一個針對對象各種行為的鉤子),拿它可以做很多有意思的事情,在我們需要對一些對象的行為進(jìn)行控制時將變得非常有效。

Proxy的語法

創(chuàng)建一個Proxy的實(shí)例需要傳入兩個參數(shù)

1.target 要被代理的對象,可以是一個object或者function

2.handlers對該代理對象的各種操作行為處理

let target = {}
let handlers = {} // do nothing
let proxy = new Proxy(target, handlers)
proxy.a = 123
console.log(target.a) // 123

在第二個參數(shù)為空對象的情況下,基本可以理解為是對第一個參數(shù)做的一次淺拷貝
(Proxy必須是淺拷貝,如果是深拷貝則會失去了代理的意義)

Traps(各種行為的代理)

就像上邊的示例代碼一樣,如果沒有定義對應(yīng)的trap,則不會起任何作用,相當(dāng)于直接操作了target。

當(dāng)我們寫了某個trap以后,在做對應(yīng)的動作時,就會觸發(fā)我們的回調(diào)函數(shù),由我們來控制被代理對象的行為。

最常用的兩個trap應(yīng)該就是get和set了。

早年JavaScript有著在定義對象時針對某個屬性進(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è)置了一個屬性_age,然后又設(shè)置了一個get age和set age。

然后我們可以直接調(diào)用obj.age來獲取一個返回值,也可以對其進(jìn)行賦值。

這么做有幾個缺點(diǎn):

1.針對每一個要代理的屬性都要編寫對應(yīng)的getter、setter。

2.必須還要存在一個存儲真實(shí)值的key(如果我們直接在getter里邊調(diào)用this.age則會出現(xiàn)堆棧溢出的情況,因?yàn)闊o論何時調(diào)用this.age進(jìn)行取值都會觸發(fā)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

我們通過創(chuàng)建get、set兩個trap來統(tǒng)一管理所有的操作,可以看到,在修改proxy的同時,target的內(nèi)容也被修改,而且我們對proxy的行為進(jìn)行了一些特殊的處理。

而且我們無需額外的用一個key來存儲真實(shí)的值,因?yàn)槲覀冊趖rap內(nèi)部操作的是target對象,而不是proxy對象。

拿Proxy來做些什么

因?yàn)樵谑褂昧薖roxy后,對象的行為基本上都是可控的,所以我們能拿來做一些之前實(shí)現(xiàn)起來比較復(fù)雜的事情。

在下邊列出了幾個簡單的適用場景。

解決對象屬性為undefined的問題

在一些層級比較深的對象屬性獲取中,如何處理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 (其實(shí)這一步已經(jīng)針對`target`創(chuàng)建了一個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)行g(shù)et的值來自一個不存在的key,則我們會在target中創(chuàng)建對應(yīng)個這個key,然后返回一個針對這個key的代理對象。

這樣就能夠保證我們的取值操作一定不會拋出can not get xxx from undefined
但是這會有一個小缺點(diǎn),就是如果你確實(shí)要判斷這個key是否存在只能夠通過in操作符來判斷,而不能夠直接通過get來判斷。

普通函數(shù)與構(gòu)造函數(shù)的兼容處理

如果我們提供了一個Class對象給其他人,或者說一個ES5版本的構(gòu)造函數(shù)。
如果沒有使用new關(guān)鍵字來調(diào)用的話,Class對象會直接拋出異常,而ES5中的構(gòu)造函數(shù)this指向則會變?yōu)檎{(diào)用函數(shù)時的作用域。
我們可以使用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的方式來調(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)用時會被觸發(fā),因?yàn)槲覀兠鞔_的知道,代理的是一個Class或構(gòu)造函數(shù),所以我們直接在apply中使用new關(guān)鍵字來調(diào)用被代理的函數(shù)。

以及如果我們想要對函數(shù)進(jìn)行限制,禁止使用new關(guān)鍵字來調(diào)用,可以用另一個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

在前端發(fā)送請求,我們現(xiàn)在經(jīng)常用到的應(yīng)該就是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進(jìn)行了一層封裝,可以直接通過.GET這種方式來調(diào)用,并設(shè)置一些通用的參數(shù)。

實(shí)現(xiàn)一個簡易的斷言工具

寫過測試的各位童鞋,應(yīng)該都會知道斷言這個東西
console.assert就是一個斷言工具,接受兩個參數(shù),如果第一個為false,則會將第二個參數(shù)作為Error message拋出。
我們可以使用Proxy來做一個直接賦值就能實(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


統(tǒng)計函數(shù)調(diào)用次數(shù)
在做服務(wù)端時,我們可以用Proxy代理一些函數(shù),來統(tǒng)計一段時間內(nèi)調(diào)用的次數(shù)。
在后期做性能分析時可能會能夠用上:

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 設(shè)置某個key值
has 使用in操作符判斷某個key是否存在
apply 函數(shù)調(diào)用,僅在代理對象為function時有效
ownKeys 獲取目標(biāo)對象所有的key
construct 函數(shù)通過實(shí)例化調(diào)用,僅在代理對象為function時有效
isExtensible 判斷對象是否可擴(kuò)展,Object.isExtensible的代理
deleteProperty 刪除一個property
defineProperty 定義一個新的property
getPrototypeOf 獲取原型對象
setPrototypeOf 設(shè)置原型對象
preventExtensions 設(shè)置對象為不可擴(kuò)展
getOwnPropertyDescriptor 獲取一個自有屬性 (不會去原型鏈查找) 的屬性描述

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI