這次帶給大家Proxy使用案例詳解,Proxy使用的注意事項有哪些,下面就是實戰案例,一起來看一下。
首先,我們要清楚,Proxy
是什麼意思,這個單字翻譯過來,就是 代理。
可以理解為,有一個很火的明星,開通了一個微博帳號,這個帳號非常活躍,回覆粉絲、到處點讚之類的,但可能並不是真的由本人在維護的。
而是在背後有一個其他人 or 團隊來運營,我們就可以稱他們為代理人,因為他們發表的微博就代表了明星本人的意思。
P.S. 強行舉例子,因為自己不追星,只是猜測可能會有這樣的營運團隊
JavaScript<a href="http://www.php.cn/wiki/48.html" target="_blank"></a>#當中來,就可以理解為對
物件或
函數的代理操作。
(在文件中被稱為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
在前端发送请求,我们现在经常用到的应该就是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) } })
这里列出了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
|
以上是Proxy使用案例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!