Skip to content
New issue

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

前端必会设计模式之(二) 代理模式 #5

Open
LyzSg opened this issue Jun 11, 2018 · 0 comments
Open

前端必会设计模式之(二) 代理模式 #5

LyzSg opened this issue Jun 11, 2018 · 0 comments

Comments

@LyzSg
Copy link
Owner

LyzSg commented Jun 11, 2018

首先我们来看一个烂大街的例子(给女神送花):

// 男的可以给女神送花
var man = {
    sendFlower: function (target) {
        var flower = 'sunflower';
        target.receiveFlower();
    }
};
// 女神可以收到花,但她有心情,而且起伏不定,只有为心情为true时收到花才ok,不然会请你吃X
var godness = {
    mood: null,
    receiveFlower: function () {
        this.mood ? console.log('ok') : console.log('yaxila');
    },
    changeMood: function () {
        this.mood = Math.random() > 0.5;
    },
    createMood: function () {
        var self = this;
        self.changeMood();
        setInterval(function () {
            self.changeMood();
        }, 400)
    }
}

godness.createMood();

man.sendFlower(godness);

这个时候,如果你直接把花送过去,那么将有一半的几率吃到X,怎么办呢?这是代理就出场了。

var proxy = {
    proxyFlower: function (target) {
        this.listenMood(target, function () {
            man.sendFlower(godness);
        })
    },
    listenMood: function (target, cb) {
        var timer = setInterval(function () {
            if(target.mood) {
                cb();
                clearInterval(timer);
            }
        }, 300)
    }
}
//man.sendFlower(godness);
proxy.proxyFlower(godness);

代理负责监听女神的心情,只有在好心情时,才让男的把花送过去。这样就能确保百分百不会吃到X。

什么是代理模式

定义:为一个对象提供一种代理以控制对这个对象的访问。

代理对象起到类似中介的作用,会增加一些功能(如,校验,合并等等),也会去掉一些原有对象的功能

分类:
虚拟代理:虚拟代理是把一些开销很大的对象,延迟到真正需要它的时候才去创建执行
安全代理:控制真实对象的访问权限
远程代理:一个对象将不同空间的对象进行局部代理
智能代理:调用对象代理处理另外一些事情如垃圾回收机制增加额外的服务

用途:
虚拟代理-图片加载,文件上传
保护代理-登录操作后才能看全功能,前端校验
远程代理-监控多个对象的状态,总机监控分店
智能代理-提供额外的其他服务 火车站代售处

在上面的送花中,proxy类似于一个虚拟代理。假设这花很贵(开销大),那么如果你随意送出去,将可能吃到X被拒绝,浪费钱钱。所以通过代理,可以延迟到女神心情好的时候(真正需要时)再送。

虚拟代理与图片预加载

了解了什么是代理模式,接下来我们来试一下利用虚拟代理来做图片的预加载。

首先,我们写一个普通的图片加载函数:

var MyImage = function (id) {
    var oImg = new Image();
    this.setSrc = function (src) {
        oImg.src = src;
    }
    document.getElementById(id).appendChild(oImg);
}
var oMyImg = new MyImage('box');
oMyImg.setSrc('new.jpg');

这里我们直接通过设置src来加载图片,但是在获取图片的过程box里是没有内容的。这时候有一个需求,为了避免box显得太空,我们需要在图片加载出来前先用一张临时的图片显示在box里,告诉用户正在加载图片。等到图片加载完后,再替换掉临时的图片。例如在淘宝首页往下拉时就是这样的预加载。

于是我们可以这样写:

var MyImage = function (id) {
    var oMyImg = new Image();
    document.getElementById(id).appendChild(oMyImg);
    var oNewImg = new Image();
    oNewImg.onload = function () {
        oMyImg.src = oNewImg.src;
    }
    this.setSrc = function (src) {
        oMyImg.src = 'temp.jpg';
        img.src = src;
    }
};
oMyImg = new MyImage('box');
oMyImg.setSrc('new.jpg');

但是这样并不好,违背了单一职责原则。例如我们只是获取一些体积非常小的图片,又或者是在未来网速快到根本不需要预加载,这时我们就不得不改动MyImage函数了。

我们更希望把原来普通加载图片的功能,和新增的功能分开,以便于我们后期的维护。于是,我们可以通过代理,把新的功能添上:

var MyImage = function (id) {
    var oImg = new Image();
    this.setSrc = function (src) {
        oImg.src = src;
    }
    document.getElementById(id).appendChild(oImg);
}

var ProxyImage = function (id) {
    var oMyImg = new MyImage(id);
    var oNewImg = new Image();
    oNewImg.onload = function () {
        oMyImg.setSrc(oNewImg.src);
    }
    this.setSrc = function (src) {
        oMyImg.setSrc('temp.jpg');
        oNewImg.src = src;
    }
}

var oProxyImg = new ProxyImage('box');
oProxyImg.setSrc('new.jpg')

这时,我们通过代理实现了相同的功能,但这时维护起来就方便的多,不需要预加载时只用把最后两句的oProxyImg改为oMyImg即可。

同时,如果有一组图片需要插入到一组DOM中,我们也可以通过一个循环实现(欢迎提供更好的办法)

var domArr = [];
var srcArr = [];
var len = domArr.length;
for(var i = 0; i < len; i ++) {
    var oProxyImg = new ProxyImage(domArr[i]);
    oProxyImg.setSrc(srcArr[i]);
}

虚拟代理与合并多个请求

我们来模拟一个情景,每点击一次按钮,我们假设他会向服务器发送一次请求,然后浏览器根据服务器返回的内容修改盒子对应的样式。

<div id="box">hello</div>
<button type="addW">add Width</button>
<button type="addC">add Color</button>
<button type="addBc">add BgColor</button>
<button type="addFs">add FontSize</button>
<button type="addFw">add FontWeight</button>
<script>
    var oBtnArr = document.getElementsByTagName('button');
    var len = oBtnArr.length;
    for (var i = 0; i < len; i++) {
        oBtnArr[i].onclick = function () {
            deal(this.getAttribute('type'));
        }
    }
    function deal(type) {
        switch (type) {
            case 'addW':
                box.style.width = box.offsetWidth + 50 + 'px';
                break;
            case 'addC':
                box.style.color = 'green';
                break;
            case 'addBc':
                box.style.backgroundColor = 'orange';
                break;
            case 'addFs':
                box.style.fontSize = '25px';
                break;
            case 'addFw':
                box.style.fontWeight = 'bold';
                break;
        }
    }

image
image
如果不加处理的话,每点击一次就发送一次请求,会很耗费性能,这种短时间内产生多次请求的行为,我们更希望把他收集到一起,然后一并发送给服务器,然后服务器把最终的结果返回给我们。这样只需要请求一次。

这时候就能用到虚拟代理的思想了,把这些请求,延迟到需要他的时候再发送。

那么,如何去写这个代理呢?首先我们不能直接调用deal,因为他会立即去发送请求,所以我们把他改造成proxyDeal,这个函数的功能是收集相当于每次执行deal时候的参数,然后,间隔一定时间后没有点击按钮(触发proxyDeal)的时候,就会把之前收集到的参数,传给deal执行,触发了多少次proxyDeal这时就执行多少次deal。

var myProxy = function () {
    /* ... */
    return function () {
        /* ... */
    }
}

var realDeal = myProxy(deal);

for (var i = 0; i < len; i++) {
    oBtnArr[i].onclick = function () {
        realDeal(this.getAttribute('type'));
    }
}
var myProxy = function (fn) {
    var cache = [];
    var timer = null;
    return function () {
        // 提高复用性,同时保存了this和arguments,即便这里并没有用到this。
        cache.push({self: this, args: arguments}); 
        clearTimeout(timer);
        timer = setTimeout(function () {
            var len = cache.length;
            for(var i = 0; i < len; i ++) {
                // 调用时改变对应的this和传入对应的参数
                fn.apply(cache[i].self, cache[i].args);
            }
            cache.length = 0;  // 清空cache,防止下一次的请求与上一次的请求叠加
        }, 1000) 
    }
}

但是这样仅仅是把请求都放到了一起一并发送,并没有减少请求的次数。

那么我们想减少请求次数怎么办呢?那就同时需要服务端的配合了,前端和服务端约定一种数据格式数组或对象,然后前端把累计的请求合并到一起,只发送一次请求,然后服务端返回的数据也是一个最终结果或结果的集合。

var myProxy = function (fn) {
    var cache = [];
    var timer = null;
    return function () {
        cache.push({self: this, args: arguments}); 
        clearTimeout(timer);
        timer = setTimeout(function () {
            fn([].concat(cache));
            cache.length = 0;
        }, 1000)
    }
}

function deal(type) {
    var arr = [];
    if(Object.prototype.toString.call(type) == '[object Array]') {
        arr = arr.concat(type);
    } else {
        arr.push({args: [type]});
    }
    var len = arr.length;
    for(var i = 0; i < len; i ++) {
        switch (arr[i].args[0]) {
            case 'addW':
                box.style.width = box.offsetWidth + 50 + 'px';
                break;
            case 'addC':
                box.style.color = 'green';
                break;
            case 'addBc':
                box.style.backgroundColor = 'orange';
                break;
            case 'addFs':
                box.style.fontSize = '25px';
                break;
            case 'addFw':
                box.style.fontWeight = 'bold';
                break;
        }
    }
}

例如我们这里把全部的参数的数组cache拷贝一份传入fn,然后fn直接处理一个参数的数组。但如果这样的话就不能每个都改变fn的this指向了。但具体情况具体分析,你也可以把this保存到cache里面。

记住,代理模式只是一种思想,他的代码不像单例模式那样的固定,我们更关注什么是代理,代理可以做哪些事情,哪些场景可以用到代理。

@LyzSg LyzSg changed the title 前端必会设计模式之(二)代理模式 前端必会设计模式之(二) 代理模式 Jun 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant