摘要:此时,链家起到的作用就是代理的作用。验证代理构造函数第二个参数中的方法,可以很方便的验证向一个对象的传值。
1 什么是代理模式
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
在生活中,代理模式的场景是十分常见的,例如我们现在如果有租房、买房的需求,更多的是去找链家等房屋中介机构,而不是直接寻找想卖房或出租房的人谈。此时,链家起到的作用就是代理的作用。链家和他所代理的客户在租房、售房上提供的方法可能都是一致的(收钱,签合同),可是链家作为代理却提供了访问限制,让我们不能直接访问被代理的客户。
在面向对象的编程中,代理模式的合理使用能够很好的体现下面两条原则:
单一职责原则: 面向对象设计中鼓励将不同的职责分布到细粒度的对象中,Proxy 在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念。
开放-封闭原则:代理可以随时从程序中去掉,而不用对其他部分的代码进行修改,在实际场景中,随着版本的迭代可能会有多种原因不再需要代理,那么就可以容易的将代理对象换成原对象的调用
2 ES6中的代理模式ES6所提供Proxy构造函数能够让我们轻松的使用代理模式:
var proxy = new Proxy(target, handler);
Proxy构造函数传入两个参数,第一个参数target表示所要代理的对象,第二个参数handler也是一个对象用来设置对所代理的对象的行为。如果想知道Proxy的具体使用方法,可参考阮一峰的《 ECMAScript入门 - Proxy 》。
本文将利用Proxy实现前端中3种代理模式的使用场景,分别是:缓存代理、验证代理、实现私有属性。
2.1 缓存代理缓存代理可以将一些开销很大的方法的运算结果进行缓存,再次调用该函数时,若参数一致,则可以直接返回缓存中的结果,而不用再重新进行运算。例如在采用后端分页的表格时,每次页码改变时需要重新请求后端数据,我们可以将页码和对应结果进行缓存,当请求同一页时就不用在进行ajax请求而是直接返回缓存中的数据。
下面我们以没有经过任何优化的计算斐波那契数列的函数来假设为开销很大的方法,这种递归调用在计算40以上的斐波那契项时就能明显的感到延迟感。
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对象实现的。这实现了我们之前提到的单一职责原则。
2.2 验证代理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的事件来实现页面中的校验提示。
2.3 实现私有属性代理模式还有一个很重要的应用是实现访问限制。总所周知,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"3 总结
ES6提供的Proxy可以让JS开发者很方便的使用代理模式,听说Vue 3.0的也会使用Proxy去大量改写核心代码。虽然代理模式很方便,但是在业务开发时应该注意使用场景,不需要在编写对象时就去预先猜测是否需要使用代理模式,只有当对象的功能变得复杂或者我们需要进行一定的访问限制时,再考虑使用代理。
参考文献[1] 掘金: 使用 Javascript 原生的 Proxy 优化应用
[2] Deal With JS: ES6 Features - 10 Use Cases for Proxy
[3] 曾探 JavaScript设计模式与开发实践 [M].r人民邮电出版社
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/96351.html
摘要:理解元编程和是属于元编程范畴的,能介入的对象底层操作进行的过程中,并加以影响。元编程中的元的概念可以理解为程序本身。中,便是两个可以用来进行元编程的特性。在之后,标准引入了,从而提供比较完善的元编程能力。 导读 几年前 ES6 刚出来的时候接触过 元编程(Metaprogramming)的概念,不过当时还没有深究。今天在应用和学习中不断接触到这概念,比如 mobx 5 中就用到了 Pr...
摘要:虚拟代理延迟执行虚拟代理的目的,是将开销大的运算延迟到需要时再执行。 showImg(https://segmentfault.com/img/bVbuitm?w=800&h=600); 代理模式:为一个对象提供一个代用品或占位符,以便控制它的访问。 当我们不方便直接访问某个对象时,或不满足需求时,可考虑使用一个替身对象来控制该对象的访问。替身对象可对请求预先进行处理,再决定是否转交给...
摘要:什么是适配器模式适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。中的适配器模式在前端项目中,适配器模式的使用场景一般有以下三种情况库的适配参数的适配和数据的适配。 1 什么是适配器模式 适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 在...
摘要:拦截实例作为构造函数调用的操作,比如。方法等同于,这提供了一种不使用,来调用构造函数的方法。方法对应,返回一个布尔值,表示当前对象是否可扩展。这是的一个提案,目前转码器已经支持。别名或修饰器在控制台显示一条警告,表示该方法将废除。 Proxy Proxy 这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器,即用自己的定义覆盖了语言的原始定义。ES6 原生提供 Proxy...
阅读 2385·2021-10-09 09:41
阅读 3176·2021-09-26 09:46
阅读 837·2021-09-03 10:34
阅读 3153·2021-08-11 11:22
阅读 3367·2019-08-30 14:12
阅读 713·2019-08-26 11:34
阅读 3348·2019-08-26 11:00
阅读 1753·2019-08-26 10:26