javascript中的设计模式之代理模式

一、定义

当客户不方便直接访问一个对象或者不满足需要的时候,提供一个对象来控制堆这个对象的访问。

二、举例

惰性单例模式的实现依靠缓存代理

三、结构

代理模式需要一个本体对象和一个代理对象。在代理模式下,对于本体对象的特定的操作通过代理对象进行。如图所示

 

 

   这种模式的关键点在于:本体对象和代理对象接口的一致性也就是说如果需要不通过代理进行操作,那么直接操作本体对象依然可以。

四、实现

代理模式分为很多类,其中经常用到的有保护代理、虚拟代理、缓存代理。

1.保护代理

保护代理的作用是当对本体对象的属性进行访问和赋值时,代理对象可以对其进行拦截。

ES6中,代理是一个已经被实现的知识点,如下:

var obj = {
    _name: "JYY",
    age: 29
};
var objProxy = new Proxy(obj, {
    get: function(target, key){
        if(key === "_name") return undefined;
        return target[key];
    },
    set: function(target, key){
        if(key === "_name") return;
    }
});
objProxy._name; // undefined
objProxy.age; // 29

上面的代码中obj是本体对象,objProxy是代理对象,这个代理实现了禁止访问和设置内部私有属性的功能。在这段代码中,我们使用代理对象对本体对象的get和set方法进行了代理,本体对象和代理对象都包含了这个set和get接口(obj对象本身内部实现了get和set),所以如果我们绕过代理对象直接访问和赋值本体对象也是可以的:

var obj = {
    _name: "JYY",
    age: 29
};
console.log(obj._name); //JYY
obj._name = "hah";
console.log(obj._name); // hah

保护代理的重点在于,代理对象保护外界对于本体对象的可访问和可操作性,也就是说在保护代理中,代理对象是用于禁止外界对本体对象的操作,防止本体对象的属性被外界进行操作

2.虚拟代理

虚拟代理在我理解,就是用户认为已经执行了某个功能,事实上却时使用代理对象进行占位,待触发的时机到来,才会真正的执行本体对象的操作。也就是虚拟代理把一些开销很大的对象,延迟到真正需要他的时候才采取创建。因此虚拟代理的使用伴随的是性能的提升。

典型的虚拟代理的例子就是节流,如下所示:

var resizeProxy = function(fn){
    let timer = null;
    return function(){
        if(!timer){
            timer = setTimeout(function(){
                fn && fn();
                timer = null;
            },1000)
        }
    }
};
var resizeChange = function(){
    console.log(1);
};
window.onresize = resizeProxy(resizeChange);

这段代码是经典的使用节流控制窗体尺寸大小改变事件触发的例子,作用就是让resize事件的触发不那么频繁,自定义的控制触发的频率,这对于性能的提升很有帮助。

在这段代码里面,resizeChange就是本体,resizeProxy就是代理,如果不使用代理直接将fn复制给window.onresize依旧是可用的。而使用代理的作用就是首先进行占位,开发中代理内部的代码是不可见的(比如提供的是api),利用resizeProxy的不可见性,让客户认为已经调用了功能代码,但是事实上是我们并未立即执行代码,因为我们作为开发者知道立即执行会带来性能的丧失,只有在合适的时机我们才会委托代理执行本地代码,执行真正的业务代码。

3.缓存代理

缓存代理在单例模式中就已出现,用于创建惰性代理模式,其原理就是在需要时创建对象,并将该对象保存在闭包中,这样可以一次创建多次使用。

当然在惰性单例中使用是缓存代理最简单的实现方式。在实际开发中,我们可能会遇到这样的场景,在tab中每个tab页都包含多个图表,因此在点击tab的时候就需要获取这个tab对应的图表的数据,这些数据都需要从后端请求。比如以某个省的地市经济统计情况为例,我们需要的页面如下:

 

可见这样的情况下,用户很可能不断地点击tab切换城市,然后和其他城市进行对比。如果每次点击都请求数据,带给用户的体验会非常差。这样的情况下,我们就可以做个全局的缓存,当然你可以保存在组件的状态中,除此之外缓存代理也是个很棒的选择。代码如下:

// 请求数据
function requestData(regionName){
    return new Promise(function(resolve, reject){
        var rest = ajax({
            type: "post",
            params: {regionName: regionName}
        })
        resolve(rest);
    })

}
// 初始化数据代理方法
var proxyInit = (function(){
    var cache = {};
    return async function(regionName){
        if(cache[regionName]){
            return cache[regionName];
        }else{
            return cache[regionName] = await requestData(regionName);
        }
    }
});
proxyInit("石家庄");

 五、总结

三种类型虽然均为代理模式,但是各自的目的并不相同,保护代理是为了阻止外部对内部对象的访问或者是操作等;虚拟代理是为了提升性能,延迟本体执行,在合适的时机进行触发,目的是减少本体的执行次数;缓存代理同样是为了提升性能,但是为了减缓内存的压力,同样的属性,在内存中只保留一份。

 

 

posted @ 2020-07-20 13:31  泛舟青烟  阅读(92)  评论(0编辑  收藏