We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
代理模式:为一个对象提供一个代用品或占位符,以便控制它的访问。
当我们不方便直接访问某个对象时,或不满足需求时,可考虑使用一个替身对象来控制该对象的访问。替身对象可对请求预先进行处理,再决定是否转交给本体对象。
生活小栗子:
经常 shopping 的同学,对代购应该不陌生。自己不方便直接购买或买不到某件商品时,会选择委托给第三方,让代购或黄牛去做购买动作。程序世界的代理者也是如此,我们不直接操作原有对象,而是委托代理者去进行。代理者的作用,就是对我们的请求预先进行处理或转接给实际对象。
JavaScript 中常用的代理模式为 “虚拟代理” 和 “缓存代理”。
实现方式:创建一个代理对象,代理对象可预先对请求进行处理,再决定是否转交给本体,代理和本体对外接口保持一致性(接口名相同)。
// 例子:代理接听电话,实现拦截黑名单 var backPhoneList = ['189XXXXX140']; // 黑名单列表 // 代理 var ProxyAcceptPhone = function(phone) { // 预处理 console.log('电话正在接入...'); if (backPhoneList.includes(phone)) { // 屏蔽 console.log('屏蔽黑名单电话'); } else { // 转接 AcceptPhone.call(this, phone); } } // 本体 var AcceptPhone = function(phone) { console.log('接听电话:', phone); }; // 外部调用代理 ProxyAcceptPhone('189XXXXX140'); ProxyAcceptPhone('189XXXXX141');
代理并不会改变本体对象,遵循 “单一职责原则”,即 “自扫门前雪,各找各家”。不同对象承担独立职责,不过于紧密耦合,具体执行功能还是本体对象,只是引入代理可以选择性地预先处理请求。例如上述代码中,我们向 “接听电话功能” 本体添加了一个屏蔽黑名单的功能(保护代理),预先处理电话接入请求。
虚拟代理的目的,是将开销大的运算延迟到需要时再执行。
虚拟代理在图片预加载的应用,代码例子来至 《JavaScript 设计模式与开发实践》
// 本体 var myImage = (function(){ var imgNode = document.createElement('img'); document.body.appendChild(imgNode); return { setSrc: function(src) { imgNode.src = src; } } })(); // 代理 var proxyImage = (function(){ var img = new Image; img.onload = function() { myImage.setSrc(this.src); // 图片加载完设置真实图片src } return { setSrc: function(src) { myImage.setSrc('./loading.gif'); // 预先设置图片src为loading图 img.src = src; } } })(); // 外部调用 proxyImage.setSrc('./product.png'); // 有loading图的图片预加载效果
缓存代理的目的,是为一些开销大的运算结果提供暂时存储,以便下次调用时,参数与结果不变情况下,从缓存返回结果,而不是重新进行本体运算,减少本体调用次数。
应用缓存代理的本体,要求运算函数应是一个纯函数,简单理解比如一个求和函数 sum, 输入参数 (1, 1), 得到的结果应该永远是 2。
sum
(1, 1)
2
纯函数:固定的输入,有固定的输出,不影响外部数据。
模拟场景:60道判断题测试,每三道题计分一次,根据计分筛选下一步的三道题目?
三道判断题得分结果:
总共七种计分结果。60/3 = 20,共进行 20 次计分,每次计分执行 3 个循环累计,共 60 个循环。接下来,借用 “缓存代理” 方式,来实现最少本体运算次数。
60/3 = 20
// 本体:对三道题答案进行计分 var countScore = function(ansList) { let [a, b, c] = ansList; return a + b + c; } // 代理:对计分请求预先处理 var proxyCountScore = (function() { var existScore = {}; // 设定存储对象 return function(ansList) { var attr = ansList.join(','); // eg. ['0,0,0'] if (existScore[attr] != null) { // 从内存返回 return existScore[attr]; } else { // 内存不存在,转交本体计算并存入内存 return existScore[attr] = countScore(ansList); } } })(); // 调用计分 proxyCountScore([0,1,0]);
60 道题目,每 3 道题一次计分,共 20 次计分运算,但总的计分结果只有 7 种,那么实际上本体 countScore() 最多只需运算 7 次,即可囊括所有计算结果。
countScore()
通过缓存代理的方式,对计分结果进行临时存储。用答案字符串组成属性名 ['0,1,0'] 作为 key 值检索内存,若存在直接从内存返回,减少包含复杂运算的本体被调用的次数。之后如果我们的题目增加至 90 道, 120 道,150 道题时,本体 countScore() 运算次数仍旧保持 7 次,中间节省了复杂运算的开销。
['0,1,0']
key
ES6新增的 Proxy 代理对象的操作,具体的实现方式是在 handler 上定义对象自定义方法集合,以便预先管控对象的操作。
Proxy
handler
ES6 的 Proxy语法:let proxyObj = new Proxy(target, handler);
// ES6的Proxy let Person = { name: '以乐之名' }; const ProxyPerson = new Proxy(Person, { get(target, key, value) { if (key != 'age') { return target[key]; } else { return '保密' } }, set(target, key, value) { if (key === 'rate') { target[key] = value === 'A' ? '推荐' : '待提高' } } }) console.log(ProxyPerson.name); // '以乐之名' console.log(ProxyPerson.age); // '保密' ProxyPerson.rate = 'A'; console.log(ProxyPerson.rate); // '推荐' ProxyPerson.rate = 'B'; console.log(ProxyPerson.rate); // '待提高'
handler 除常用的 set/get,总共支持 13 种方法:
set/get
handler.getPrototypeOf() // 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时 handler.setPrototypeOf() // 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时 handler.isExtensible() // 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时 handler.preventExtensions() // 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时 handler.getOwnPropertyDescriptor() // 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时 handler.defineProperty() // 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时 handler.has() // 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时 handler.get() // 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时 handler.set() // 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时 handler.deleteProperty() // 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时 handler.ownKeys() // 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时 handler.apply() // 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。 handler.construct() // 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行 new proxy() 时
“策略模式” 可应用于表单验证信息,“代理方式” 也可实现。这里引用 Github - jawil 的一个例子,思路供大家分享。
// 利用 proxy 拦截格式不符数据 function validator(target, validator, errorMsg) { return new Proxy(target, { _validator: validator, set(target, key, value, proxy) { let errMsg = errorMsg; if (value == null || !value.length) { console.log(`${errMsg[key]} 不能为空`); return target[key] = false; } let va = this._validator[key]; // 这里有策略模式的应用 if (!!va(value)) { return Reflect.set(target, key, value, proxy); } else { console.log(`${errMsg[key]} 格式不正确`); return target[key] = false; } } }) } // 负责校验的逻辑代码 const validators = { name(value) { return value.length >= 6; }, passwd(value) { return value.length >= 6; }, moblie(value) { return /^1(3|5|7|8|9)[0-9]{9}$/.test(value); }, email(value) { return /^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value) } } // 调用代码 const errorMsg = { name: '用户名', passwd: '密码', moblie: '手机号码', email: '邮箱地址' } const vali = validator({}, validators, errorMsg) let registerForm = document.querySelector('#registerForm') registerForm.addEventListener('submit', function () { let validatorNext = function* () { yield vali.name = registerForm.userName.value yield vali.passwd = registerForm.passWord.value yield vali.moblie = registerForm.phone.value yield vali.email = registerForm.email.value } let validator = validatorNext(); for (let field of validator) { validator.next(); } }
实现思路: 利用 ES6 的 proxy 自定义 handler 的 set() ,进行表单校验并返回结果,并且借用 “策略模式" 独立封装验证逻辑。使得表单对象,验证逻辑,验证器各自独立。代码整洁性,维护性及复用性都得到增强。
set()
关于 “设计模式” 在表单验证的应用,可参考 jawil 原文:《探索两种优雅的表单验证——策略设计模式和ES6的Proxy代理模式》。
参考文章
本文首发Github,期待Star! https://github.com/ZengLingYong/blog
作者:以乐之名 本文原创,有不当的地方欢迎指出。转载请指明出处。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
当我们不方便直接访问某个对象时,或不满足需求时,可考虑使用一个替身对象来控制该对象的访问。替身对象可对请求预先进行处理,再决定是否转交给本体对象。
生活小栗子:
经常 shopping 的同学,对代购应该不陌生。自己不方便直接购买或买不到某件商品时,会选择委托给第三方,让代购或黄牛去做购买动作。程序世界的代理者也是如此,我们不直接操作原有对象,而是委托代理者去进行。代理者的作用,就是对我们的请求预先进行处理或转接给实际对象。
模式特点
模式细分
JavaScript 中常用的代理模式为 “虚拟代理” 和 “缓存代理”。
模式实现
代理并不会改变本体对象,遵循 “单一职责原则”,即 “自扫门前雪,各找各家”。不同对象承担独立职责,不过于紧密耦合,具体执行功能还是本体对象,只是引入代理可以选择性地预先处理请求。例如上述代码中,我们向 “接听电话功能” 本体添加了一个屏蔽黑名单的功能(保护代理),预先处理电话接入请求。
虚拟代理(延迟执行)
虚拟代理的目的,是将开销大的运算延迟到需要时再执行。
虚拟代理在图片预加载的应用,代码例子来至 《JavaScript 设计模式与开发实践》
缓存代理(暂时存储)
缓存代理的目的,是为一些开销大的运算结果提供暂时存储,以便下次调用时,参数与结果不变情况下,从缓存返回结果,而不是重新进行本体运算,减少本体调用次数。
应用缓存代理的本体,要求运算函数应是一个纯函数,简单理解比如一个求和函数
sum
, 输入参数(1, 1)
, 得到的结果应该永远是2
。模拟场景:60道判断题测试,每三道题计分一次,根据计分筛选下一步的三道题目?
三道判断题得分结果:
总共七种计分结果。
60/3 = 20
,共进行 20 次计分,每次计分执行 3 个循环累计,共 60 个循环。接下来,借用 “缓存代理” 方式,来实现最少本体运算次数。60 道题目,每 3 道题一次计分,共 20 次计分运算,但总的计分结果只有 7 种,那么实际上本体
countScore()
最多只需运算 7 次,即可囊括所有计算结果。通过缓存代理的方式,对计分结果进行临时存储。用答案字符串组成属性名
['0,1,0']
作为key
值检索内存,若存在直接从内存返回,减少包含复杂运算的本体被调用的次数。之后如果我们的题目增加至 90 道, 120 道,150 道题时,本体countScore()
运算次数仍旧保持 7 次,中间节省了复杂运算的开销。ES6 的 Proxy
ES6新增的
Proxy
代理对象的操作,具体的实现方式是在handler
上定义对象自定义方法集合,以便预先管控对象的操作。handler
预处理handler
除常用的set/get
,总共支持 13 种方法:适用场景
“策略模式” 可应用于表单验证信息,“代理方式” 也可实现。这里引用 Github - jawil 的一个例子,思路供大家分享。
实现思路: 利用 ES6 的 proxy 自定义
handler
的set()
,进行表单校验并返回结果,并且借用 “策略模式" 独立封装验证逻辑。使得表单对象,验证逻辑,验证器各自独立。代码整洁性,维护性及复用性都得到增强。关于 “设计模式” 在表单验证的应用,可参考 jawil 原文:《探索两种优雅的表单验证——策略设计模式和ES6的Proxy代理模式》。
优缺点
参考文章
本文首发Github,期待Star!
https://github.com/ZengLingYong/blog
The text was updated successfully, but these errors were encountered: