为其余对象提供一种代理以控制对这个对象的访问。在某些状况下,一个对象不适合或者不能直接引用另外一个对象,而代理对象能够在客户端和目标对象之间起到中介的做用。
在生活中,代理模式的场景是十分常见的,例如咱们如今若是有租房、买房的需求,更多的是去找链家等房屋中介机构,而不是直接寻找想卖房或出租房的人谈。此时,链家起到的做用就是代理的做用。链家和他所代理的客户在租房、售房上提供的方法可能都是一致的(收钱,签合同),但是链家做为代理却提供了访问限制,让咱们不能直接访问被代理的客户。前端
在面向对象的编程中,代理模式的合理使用可以很好的体现下面两条原则:es6
ES6所提供Proxy
构造函数可以让咱们轻松的使用代理模式:ajax
var proxy = new Proxy(target, handler);
Proxy
构造函数传入两个参数,第一个参数target
表示所要代理的对象,第二个参数handler
也是一个对象用来设置对所代理的对象的行为。若是想知道Proxy
的具体使用方法,可参考阮一峰的《 ECMAScript入门 - Proxy 》。编程
本文将利用Proxy
实现前端中3种代理模式的使用场景,分别是:缓存代理、验证代理、实现私有属性。后端
缓存代理能够将一些开销很大的方法的运算结果进行缓存,再次调用该函数时,若参数一致,则能够直接返回缓存中的结果,而不用再从新进行运算。例如在采用后端分页的表格时,每次页码改变时须要从新请求后端数据,咱们能够将页码和对应结果进行缓存,当请求同一页时就不用在进行ajax请求而是直接返回缓存中的数据。设计模式
下面咱们以没有通过任何优化的计算斐波那契数列的函数来假设为开销很大的方法,这种递归调用在计算40以上的斐波那契项时就能明显的感到延迟感。缓存
const getFib = (number) => { if (number <= 2) { return 1; } else { return getFib(number - 1) + getFib(number - 2); } }
如今咱们来写一个建立缓存代理的工厂函数:app
const getCacheProxy = (fn, cache = new Map()) => { return new Proxy(fn, { apply(target, context, args) { const argsString = args.join(' '); if (cache.has(argsString)) { // 若是有缓存,直接返回缓存数据 console.log(`输出${args}的缓存结果: ${cache.get(argsString)}`); return cache.get(argsString); } const result = fn(...args); cache.set(argsString, result); return result; } }) }
调用方法以下:函数
const getFibProxy = getCacheProxy(getFib); getFibProxy(40); // 102334155 getFibProxy(40); // 输出40的缓存结果: 102334155
当咱们第二次调用getFibProxy(40)
时,getFib
函数并无被调用,而是直接从cache
中返回了以前被缓存好的计算结果。经过加入缓存代理的方式,getFib
只须要专一于本身计算斐波那契数列的职责,缓存的功能使由Proxy
对象实现的。这实现了咱们以前提到的单一职责原则。post
Proxy
构造函数第二个参数中的set
方法,能够很方便的验证向一个对象的传值。咱们以一个传统的登录表单举例,该表单对象有两个属性,分别是account
和password
,每一个属性值都有一个简单和其属性名对应的验证方法,验证规则以下:
// 表单对象 const userForm = { account: '', password: '', } // 验证方法 const validators = { account(value) { // account 只容许为中文 const re = /^[\u4e00-\u9fa5]+$/; return { valid: re.test(value), error: '"account" is only allowed to be Chinese' } }, password(value) { // password 的长度应该大于6个字符 return { valid: value.length >= 6, error: '"password "should more than 6 character' } } }
下面咱们来使用Proxy
实现一个通用的表单验证器
const getValidateProxy = (target, validators) => { return new Proxy(target, { _validators: validators, set(target, prop, value) { if (value === '') { console.error(`"${prop}" is not allowed to be empty`); return target[prop] = false; } const validResult = this._validators[prop](value); if(validResult.valid) { return Reflect.set(target, prop, value); } else { console.error(`${validResult.error}`); return target[prop] = false; } } }) }
调用方式以下
const userFormProxy = getValidateProxy(userForm, validators); userFormProxy.account = '123'; // "account" is only allowed to be Chinese userFormProxy.password = 'he'; // "password "should more than 6 character
咱们调用getValidateProxy
方法去生成了一个代理对象userFormProxy
,该对象在设置属性的时候会根据validators
的验证规则对值进行校验。这咱们使用的是console.error
抛出错误信息,固然咱们也能够加入对DOM的事件来实现页面中的校验提示。
代理模式还有一个很重要的应用是实现访问限制。总所周知,JavaScript是没有私有属性这一个概念的,一般私有属性的实现是经过函数做用域中变量实现的,虽然实现了私有属性,但对于可读性来讲并很差。
私有属性通常是以_
下划线开头,经过Proxy
构造函数中的第二个参数所提供的方法,咱们能够很好的去限制以_
开头的属性的访问。
下面我来实现getPrivateProps
这个函数,该函数的第一个参数obj
是所被代理的对象,第二个参数filterFunc
是过滤访问属性的函数,目前该函数的做用是用来限制以_
开头的属性访问。
function getPrivateProps(obj, filterFunc) { return new Proxy(obj, { get(obj, prop) { if (!filterFunc(prop)) { let value = Reflect.get(obj, prop); // 若是是方法, 将this指向修改原对象 if (typeof value === 'function') { value = value.bind(obj); } return value; } }, set(obj, prop, value) { if (filterFunc(prop)) { throw new TypeError(`Can't set property "${prop}"`); } return Reflect.set(obj, prop, value); }, has(obj, prop) { return filterFunc(prop) ? false : Reflect.has(obj, prop); }, ownKeys(obj) { return Reflect.ownKeys(obj).filter(prop => !filterFunc(prop)); }, getOwnPropertyDescriptor(obj, prop) { return filterFunc(prop) ? undefined : Reflect.getOwnPropertyDescriptor(obj, prop); } }); } function propFilter(prop) { return prop.indexOf('_') === 0; }
在上面的getPrivateProps
方法的内部实现中, Proxy
的第二个参数中咱们使用了提供的get
,set
,has
,ownKeys
, getOwnPropertyDescriptor
这些方法,这些方法的做用其实本质都是去最大限度的限制私有属性的访问。其中在get
方法的内部,咱们有个判断,若是访问的是对象方法使将this
指向被代理对象,这是在使用Proxy
须要十分注意的,若是不这么作方法内部的this
会指向Proxy
代理。
下面来看一下getPrivateProps
的调用方法,并验证其代理提供的访问控制的能力。
const myObj = { public: 'hello', _private: 'secret', method: function () { console.log(this._private); } }, myProxy = getPrivateProps(myObj, propFilter); console.log(JSON.stringify(myProxy)); // {"public":"hello"} console.log(myProxy._private); // undefined console.log('_private' in myProxy); // false console.log(Object.keys(myProxy)); // ["public", "method"] for (let prop in myProxy) { console.log(prop); } // public method myProxy._private = 1; // Uncaught TypeError: Can't set property "_private"
ES6提供的Proxy
可让JS开发者很方便的使用代理模式,据说Vue 3.0的也会使用Proxy
去大量改写核心代码。虽然代理模式很方便,可是在业务开发时应该注意使用场景,不须要在编写对象时就去预先猜想是否须要使用代理模式,只有当对象的功能变得复杂或者咱们须要进行必定的访问限制时,再考虑使用代理。