javascript设计模式:代理模式

代理模式 (Proxy Pattern)又称委托模式,它为目标对象创造了一个代理对象,以控制对目标对象的访问。

代理模式把代理对象插入到访问者和目标对象之间,从而为访问者对目标对象的访问引入一定的间接性。正是这种间接性,给了代理对象很多操作空间,比如在调用目标对象前和调用后进行一些预操作和后操作,从而实现新的功能或者扩展目标的功能。

一、代理模式例子:

明星总是有个助理,或者说经纪人,如果某导演来请这个明星演出,或者某个品牌来找明星做广告,需要经纪人帮明星做接洽工作。而且经纪人也起到过滤的作用,毕竟明星也不是什么电影和广告都会接。

打官司是件非常麻烦的事,包括查找法律条文、起草法律文书、法庭辩论、签署法律文件、申请法院执行等等流程。此时,当事人就可聘请代理律师来完成整个打官司的所有事务。当事人只需与代理律师签订全权委托协议,那么整个打官司的过程,当事人都可以不用出现。法院的一些复杂事务都可以通过代理律师来完成,而法院需要当事人完成某些工作的时候,比如出庭,代理律师才会通知当事人,并为当事人出谋划策。

在类似的场景中,有以下特点:

  1. 导演/法院(访问者)对明星/当事人(目标)的访问都是通过经纪人/律师(代理)来完成;
  2. 经纪人/律师(代理)对访问有过滤的功能;

二、代码实现

//目标对象target: 明星
const SuperStar = {
    name: '小鲜肉',
    scheduleFlag: false,
    scheduleFlagActually: false,
    playAdvertisement: function (advertisement) {
        console.log(this.name + '做任务: ' + advertisement)
    }
}

//代理对象proxy: 经纪人
const proxyAssistant = {
    name: '经纪人张某',
    scheduleTimeByPromise: function () {
        return new Promise((resolve, reject) => {
            console.log("schedule time")
            setTimeout(() => {
                console.log("小鲜肉空出时间了");
                resolve();
            }, 2000)
        })
    },
    scheduleTimeByProxy: function (advertisement) {
        const schedule = new Proxy(SuperStar, {
            // handler回调,prop为发生变化的属性,val为发生变化的值
            // 有值发生变化后才执行回调,此处setTimeout先执行
            // 只要对象属性发生变化就会监听执行回调
            set(obj, prop, val) {
                console.log('proxy values: ', prop, val)
                if (prop !== 'scheduleFlag') {
                    return;
                }
                if (obj.scheduleFlag === false && val === true) {
                    obj.scheduleFlag = true;
                    obj.playAdvertisement(advertisement);
                }
            }
        });
        setTimeout(() => {
            console.log("小鲜肉空出时间了");
            schedule.scheduleFlag = true;
        }, 2000)
    },
    scheduleTimeByDefineProperty: function (advertisement) {
        // 只能监听scheduleFlag变化,还要借用变量
        Object.defineProperty(SuperStar, 'scheduleFlag', {
            get() {
                return SuperStar.scheduleFlagActually
            },
            set(val) {
                if (SuperStar.scheduleFlagActually === false && val === true) {
                    SuperStar.scheduleFlagActually = true;
                    SuperStar.playAdvertisement(advertisement)
                }
            }
        });

        setTimeout(() => {
            console.log("小鲜肉空出时间了");
            SuperStar.scheduleFlag = true;
        }, 2000)
    },
    playAdvertisement: function (condition, advertisement) {
        if (condition > 100000) {
            console.log('接受任务: ' + advertisement);
            //SuperStar.playAdvertisement(advertisement);
            // proxyAssistant.scheduleTimeByPromise()
            // .then(()=> {
            //     SuperStar.playAdvertisement(advertisement)
            // });
            proxyAssistant.scheduleTimeByProxy(advertisement);
        } else {
            console.log('不满足条件,不做')
        }
    }
}
//访问者visitor
proxyAssistant.playAdvertisement(100, "拍广告");
proxyAssistant.playAdvertisement(1000000, "拍广告");

三、代理模式在实战中的应用

  • 拦截器

拦截器的思想在实战中应用非常多,比如我们在项目中经常使用 Axios 的实例来进行 HTTP 的请求,使用拦截器 interceptor 可以提前对 request 请求和 response 返回进行一些预处理,比如:

  1. request 请求头的设置,和 Cookie 信息的设置;
  2. 权限信息的预处理,常见的比如验权操作或者 Token 验证;
  3. 数据格式的格式化,比如对组件绑定的 Date 类型的数据在请求前进行一些格式约定好的序列化操作;
  4. 空字段的格式预处理,根据后端进行一些过滤操作;
  5. response 的一些通用报错处理,比如使用 Message 控件抛出错误;

 除了 HTTP 相关的拦截器之外,还有 vue-router、react-router 路由跳转的拦截器,可以进行一些路由跳转的预处理等操作。

拦截器看起来似乎和装饰者模式很像,但是要注意装饰者模式和代理模式的区别,代理模式控制访问者对目标对象的访问,而装饰者模式只给目标对象添加功能,原有功能不变且可直接使用。Axios 拦截器是可以取消请求的,vue-router 路由拦截器也可以进行路由截停和重定向等等复杂操作,这些场景下,无疑是代理模式,因为这里的拦截器控制了对目标对象的访问,如果没有进行访问控制而只进行消息预处理和后处理,那么则可以当作是装饰者模式。

  • 前端框架的数据响应式化

现在的很多前端框架或者状态管理框架都使用上面介绍的 Object.defineProperty 和 Proxy 来实现数据的响应式化,比如 Vue、Mobx、AvalonJS 等,Vue 2.x 与 AvalonJS 使用前者,而 Vue 3.x 与 Mobx 5.x 使用后者。

Vue 2.x 中通过 Object.defineProperty 来劫持各个属性的 setter/getter,在数据变动时,通过发布-订阅模式发布消息给订阅者,触发相应的监听回调,从而实现数据的响应式化,也就是数据到视图的双向绑定。

为什么 Vue 2.x 到 3.x 要从 Object.defineProperty 改用 Proxy 呢,是因为前者的一些局限性,导致的以下缺陷:

  1. 无法监听利用索引直接设置数组的一个项,例如:vm.items[indexOfItem] = newValue
  2. 无法监听数组的长度的修改,例如:vm.items.length = newLength
  3. 无法监听 ES6 的 SetWeakSetMapWeakMap 的变化;
  4. 无法监听 Class 类型的数据;
  5. 无法监听对象属性的新加或者删除;

除此之外还有性能上的差异,基于这些原因,Vue 3.x 改用 Proxy 来实现数据监听了。当然缺点就是对 IE 用户的不友好,兼容性敏感的场景需要做一些取舍。

  • 正向代理与反向代理

反向代理对应的是正向代理(Forward Proxy),他们的区别是:

  1. 正向代理: 一般的访问流程是客户端直接向目标服务器发送请求并获取内容,使用正向代理后,客户端改为向代理服务器发送请求,并指定目标服务器(原始服务器),然后由代理服务器和原始服务器通信,转交请求并获得的内容,再返回给客户端。正向代理隐藏了真实的客户端,为客户端收发请求,使真实客户端对服务器不可见;
  2. 反向代理: 与一般访问流程相比,使用反向代理后,直接收到请求的服务器是代理服务器,然后将请求转发给内部网络上真正进行处理的服务器,得到的结果返回给客户端。反向代理隐藏了真实的服务器,为服务器收发请求,使真实服务器对客户端不可见。

反向代理一般在处理跨域请求的时候比较常用,属于服务端开发人员的日常操作了,另外在缓存服务器、负载均衡服务器等等场景也是使用到代理模式的思想。

 
原文地址:https://www.cnblogs.com/Nyan-Workflow-FC/p/13039653.html