首先,咱们要清楚,Proxy
是什么意思,这个单词翻译过来,就是 代理。
能够理解为,有一个很火的明星,开通了一个微博帐号,这个帐号很是活跃,回复粉丝、处处点赞之类的,但可能并非真的由本人在维护的。
而是在背后有一个其余人 or 团队来运营,咱们就能够称他们为代理人,由于他们发表的微博就表明了明星本人的意思。
P.S. 强行举例子,由于本人不追星,只是猜想可能会有这样的运营团队javascript
这个代入到JavaScript
当中来,就能够理解为对对象
或者函数
的代理操做。前端
Proxy是ES6中提供的新的API,能够用来定义对象各类基本操做的自定义行为
(在文档中被称为traps
,我以为能够理解为一个针对对象各类行为的钩子)
拿它能够作不少有意思的事情,在咱们须要对一些对象的行为进行控制时将变得很是有效。java
建立一个Proxy
的实例须要传入两个参数json
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必须是浅拷贝,若是是深拷贝则会失去了代理的意义)app
就像上边的示例代码同样,若是没有定义对应的trap
,则不会起任何做用,至关于直接操做了target
。
当咱们写了某个trap
之后,在作对应的动做时,就会触发咱们的回调函数,由咱们来控制被代理对象的行为。cors
最经常使用的两个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 |
getPrototypeOf | 获取原型对象 |
setPrototypeOf | 设置原型对象 |
preventExtensions | 设置对象为不可扩展 |
getOwnPropertyDescriptor | 获取一个自有属性 (不会去原型链查找) 的属性描述 |