代理模式的定义
代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
(现实生活中,代理模式随处可见,比如买房租房的中介链家,就是代理)
用途:控制对指定对象的访问(对外界的访问进行过滤/改写)
使用场景:缓存代理、验证代理、实现私有属性
实现方式:使用ES6提供的Proxy
构造函数可以轻松实现代理模式
(Proxy的用法详见
https://es6.ruanyifeng.com/#docs/proxy)
注意事项:虽然代理模式很方便,但是在业务开发时应该注意使用场景,不需要在编写对象时就去预先猜测是否需要使用代理模式,只有当对象的功能变得复杂或者我们需要进行一定的访问限制时,再考虑使用代理。
在面向对象的编程中,代理模式的合理使用能够很好的体现下面两条原则:
- 单一职责原则: 面向对象设计中鼓励将不同的职责分布到细粒度的对象中,Proxy 在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念。
- 开放-封闭原则:代理可以随时从程序中去掉,而不用对其他部分的代码进行修改,在实际场景中,随着版本的迭代可能会有多种原因不再需要代理,那么就可以容易的将代理对象换成原对象的调用
演示范例——缓存代理
缓存代理将一些开销很大的方法的运算结果进行缓存,再次调用该函数时,若参数一致,则可以直接返回缓存中的结果,而不用再重新进行运算。
// 计算斐波那契数列的函数 const getFib = (number) => { if (number <= 2) { return 1; } else { return getFib(number - 1) + getFib(number - 2); } } // 创建缓存代理的工厂函数 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
对象实现的。
演示范例——验证代理
Proxy
构造函数第二个参数中的set
方法,可以很方便的验证向一个对象的传值。
以登录表单为例:
// 表单对象 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
的验证规则对值进行校验。
演示范例——实现私有属性
私有属性一般是以_下划线开头,通过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; } // 调用方法,验证其代理提供的访问控制的能力 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"
在上面的getPrivateProps方法的内部实现中, Proxy的第二个参数中我们使用了提供的get,set,has,ownKeys, getOwnPropertyDescriptor这些方法,这些方法的作用其实本质都是去最大限度的限制私有属性的访问。其中在get方法的内部,我们有个判断,如果访问的是对象方法使将this指向被代理对象,这是在使用Proxy需要十分注意的,如果不这么做方法内部的this会指向Proxy代理。
更多设计模式详见——js设计模式【详解】总目录
https://blog.csdn.net/weixin_41192489/article/details/116154815