js设计模式【详解】—— 代理模式

简介: js设计模式【详解】—— 代理模式

代理模式的定义

代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

(现实生活中,代理模式随处可见,比如买房租房的中介链家,就是代理)

 

用途:控制对指定对象的访问(对外界的访问进行过滤/改写)

使用场景:缓存代理验证代理实现私有属性

实现方式:使用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

目录
相关文章
|
2月前
|
设计模式 JavaScript 算法
浅谈几种js设计模式
设计模式是软件开发中的宝贵工具,能够提高代码的可维护性和扩展性。通过单例模式、工厂模式、观察者模式和策略模式,我们可以解决不同场景下的实际问题,编写更加优雅和高效的代码。
40 8
|
3月前
|
设计模式 缓存 Java
「全网最细 + 实战源码案例」设计模式——代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,通过代理对象控制对目标对象的访问并添加额外功能。它分为静态代理和动态代理,后者包括JDK动态代理和CGLIB动态代理。JDK动态代理基于接口反射生成代理类,而CGLIB通过继承目标类生成子类。代理模式适用于延迟初始化、访问控制、远程服务、日志记录和缓存等场景,优点是职责分离、符合开闭原则和提高安全性,缺点是增加系统复杂性。
101 25
|
3月前
|
设计模式 数据安全/隐私保护
Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式
这篇文章介绍了在Next.js框架下,如何处理中后台管理系统中特殊页面(如登录页)不包裹根布局(RootLayout)的问题。作者指出Next.js的设计理念是通过布局的嵌套来创建复杂的页面结构,这虽然保持了代码的整洁和可维护性,但对于特殊页面来说,却造成了不必要的布局包裹。文章提出了一个解决方案,即通过判断页面的skipGlobalLayout属性来决定是否包含RootLayout,从而实现特殊页面不包裹根布局的目标。
164 33
|
4月前
|
设计模式 前端开发 数据安全/隐私保护
前端必须掌握的设计模式——代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,通过引入“替身”对象来间接访问真实对象,从而解耦并提升性能和安全性。例如,知名艺人复出后,经纪人作为代理筛选商单,确保只处理符合团队利益的请求。代码实现中,定义接口`IService`,艺人和经纪人都实现该接口,经纪人在访问时进行过滤和转发。代理模式常用于权限控制、性能优化等场景,如前端中的Tree-shaking和ES6的Proxy构造方法。
前端必须掌握的设计模式——代理模式
|
5月前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
80 2
|
7月前
|
设计模式 缓存 安全
设计模式——代理模式
静态代理、JDK动态代理、Cglib 代理
设计模式——代理模式
|
6月前
|
设计模式 JavaScript 前端开发
JavaScript设计模式--访问者模式
【10月更文挑战第1天】
64 3
|
7月前
|
设计模式 Java 数据安全/隐私保护
Java设计模式-代理模式(7)
Java设计模式-代理模式(7)
|
4月前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
246 11
|
5月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
下一篇
oss创建bucket