什么是代理模式
代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式。
所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、内存中的大对象、文件或其它昂贵或无法复制的资源。
著名的代理模式例子为引用计数(英语:reference counting)指针对象。
当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少内存用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。
上面是维基百科中对代理模式的一个整体的定义.而在JavaScript中代理模式的具体表现形式就是ES6中的新增对象 — Proxy
Proxy 介绍
在MDN上对于Proxy的解释是:
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
Proxy
用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”
Proxy的使用场景
ES6原生提供了Proxy构造函数,用来生成Proxy实例。
1
| let proxy = new Proxy(target, handler);
|
Proxy
对象的所有用法,都是上面的这种形式。不同的只是handle
参数的写法。其中new Proxy
用来生成Proxy
实例,target
是表示所要拦截的对象,handle是
用来定制拦截行为的对象
下面是简单的Proxy例子,使用的是get代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let target = { x: 10, y: 20 }
let handler = { get: (obj, prop) => 42 }
target = new Proxy(target, handler)
target.x //42 target.y //42 target.x // 42
|
结果是一个对象将为任何属性访问操作都返回“42”。 这包括target.x
,target['x']
,Reflect.get(target, 'x')
等。
但是,Proxy 当然不限于属性的读取。 它只是十几个不同代理中的一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| handler.getPrototypeOf()
handler.setPrototypeOf()
handler.isExtensible()
handler.preventExtensions()
handler.getOwnPropertyDescriptor()
handler.defineProperty()
handler.has()
handler.get()
handler.set()
handler.deleteProperty()
handler.ownKeys()
handler.apply()
handler.construct()
|
具有代理的API - 复杂的示例
通过使用简单用例中的知识,我们可以创建一个API包装器,以便在我们的应用程序中使用。 当前只支持 get 和 post 请求,但它可以很容易地扩展。代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| const api = new Proxy({}, { get(target, key, context) { if (target[key]) { return target[key] } return ['get', 'post'].reduce((acc, key) => { acc[key] = (config, data) => { if (!config || !config.url || config.url === '') { throw new Error('Url cannot be empty.'); } let isPost = key === 'post'; if (isPost && !data) throw new Error('Please provide data in JSON format when using POST request.'); config.headers = isPost ? Object.assign(config.headers || {}, { 'content-type': 'application/json;chartset=utf8' }) : config.headers; return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open(key, config.url); if (config.headers) { Object.keys(config.headers).forEach((header) => { xhr.setRequestHeader(header, config.headers[header]); }); } xhr.onload = () => (xhr.status === 200 ? resolve : reject)(xhr); xhr.onerror = () => reject(xhr); xhr.send(isPost ? JSON.stringify(data) : null); }) } }, target)[key] }, set() { throw new Error('API methods are readonly'); }, deleteProperty() { throw new Error('API methods cannot be deleted!'); } api.get({ url: 'my-url' }).then((xhr) => { alert('Success'); }, (xhr) => { alert('Fail'); }); })
|
get
在这里很有趣,它做了几件事。target
是一个空对象,get
方法将在第一次有人使用 api 时创建所有方法(如当前的 get 和 post请求),在 reduce 回调中,我们根据提供的配置执行API规范所需的验证和检查。在此示例中,我们不允许空URL和发布请求而不提供数据。这些检查可以扩展和修改,但重要的是我们只能在这一个地方集中处理。
reduce
仅在第一次API调用时完成,之后都会跳过整个 reduce 进程,get 只会执行默认行为并返回属性值,即API处理程序。每个处理程序返回一个Promise对象,负责创建请求并调用服务
结语
当您需要对数据进行更多控制时,代理可以派上用场。你可以根据受控规则扩展或拒绝对原始数据的访问,从而监视对象并确保正确行为。