深入理解JavaScript系列(31):设计模式之代理模式详解


Posted in Javascript onMarch 03, 2015

介绍

代理,顾名思义就是帮助别人做事,GoF对代理模式的定义如下:

代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。

代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。

正文

我们来举一个简单的例子,假如dudu要送酸奶小妹玫瑰花,却不知道她的联系方式或者不好意思,想委托大叔去送这些玫瑰,那大叔就是个代理(其实挺好的,可以扣几朵给媳妇),那我们如何来做呢?

// 先声明美女对象

var girl = function (name) {

    this.name = name;

};
// 这是dudu

var dudu = function (girl) {

    this.girl = girl;

    this.sendGift = function (gift) {

        alert("Hi " + girl.name + ", dudu送你一个礼物:" + gift);

    }

};
// 大叔是代理

var proxyTom = function (girl) {

    this.girl = girl;

    this.sendGift = function (gift) {

        (new dudu(girl)).sendGift(gift); // 替dudu送花咯

    }

};

调用方式就非常简单了:

var proxy = new proxyTom(new girl("酸奶小妹"));

proxy.sendGift("999朵玫瑰");

实战一把

通过上面的代码,相信大家对代理模式已经非常清楚了,我们来实战下:我们有一个简单的播放列表,需要在点击单个连接(或者全选)的时候在该连接下方显示视频曲介绍以及play按钮,点击play按钮的时候播放视频,列表结构如下:

<h1>Dave Matthews vids</h1>

<p><span id="toggle-all">全选/反选</span></p>

<ol id="vids">

  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--2158073">Gravedigger</a></li>

  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--4472739">Save Me</a></li>

  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--45286339">Crush</a></li>

  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--2144530">Don't Drink The Water</a></li>

  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--217241800">Funny the Way It Is</a></li>

  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--2144532">What Would You Say</a>

</li>

</ol>

我们先来分析如下,首先我们不仅要监控a连接的点击事件,还要监控“全选/反选”的点击事件,然后请求服务器查询视频信息,组装HTML信息显示在li元素的最后位置上,效果如下:

深入理解JavaScript系列(31):设计模式之代理模式详解

然后再监控play连接的点击事件,点击以后开始播放,效果如下:

深入理解JavaScript系列(31):设计模式之代理模式详解

好了,开始,没有jQuery,我们自定义一个选择器:

var $ = function (id) {

    return document.getElementById(id);

};

由于Yahoo的json服务提供了callback参数,所以我们传入我们自定义的callback以便来接受数据,具体查询字符串拼装代码如下:
var http = {

    makeRequest: function (ids, callback) {

        var url = 'http://query.yahooapis.com/v1/public/yql?q=',

            sql = 'select * from music.video.id where ids IN ("%ID%")',

            format = "format=json",

            handler = "callback=" + callback,

            script = document.createElement('script');
            sql = sql.replace('%ID%', ids.join('","'));

            sql = encodeURIComponent(sql);
            url += sql + '&' + format + '&' + handler;

            script.src = url;
        document.body.appendChild(script);

    }

};

代理对象如下:

var proxy = {

    ids: [],

    delay: 50,

    timeout: null,

    callback: null,

    context: null,

    // 设置请求的id和callback以便在播放的时候触发回调

    makeRequest: function (id, callback, context) {
        // 添加到队列dd to the queue

        this.ids.push(id);
        this.callback = callback;

        this.context = context;
        // 设置timeout

        if (!this.timeout) {

            this.timeout = setTimeout(function () {

                proxy.flush();

            }, this.delay);

        }

    },

    // 触发请求,使用代理职责调用了http.makeRequest

    flush: function () {

        // proxy.handler为请求yahoo时的callback

        http.makeRequest(this.ids, 'proxy.handler'); 

        // 请求数据以后,紧接着执行proxy.handler方法(里面有另一个设置的callback)

        

        // 清楚timeout和队列

        this.timeout = null;

        this.ids = [];
    },

    handler: function (data) {

        var i, max;
        // 单个视频的callback调用

        if (parseInt(data.query.count, 10) === 1) {

            proxy.callback.call(proxy.context, data.query.results.Video);

            return;

        }
        // 多个视频的callback调用

        for (i = 0, max = data.query.results.Video.length; i < max; i += 1) {

            proxy.callback.call(proxy.context, data.query.results.Video[i]);

        }

    }

};

视频处理模块主要有3种子功能:获取信息、展示信息、播放视频:

var videos = {

    // 初始化播放器代码,开始播放

    getPlayer: function (id) {

        return '' +

            '<object width="400" height="255" id="uvp_fop" allowFullScreen="true">' +

            '<param name="movie" value="http://d.yimg.com/m/up/fop/embedflv/swf/fop.swf"\/>' +

            '<param name="flashVars" value="id=v' + id + '&eID=1301797&lang=us&enableFullScreen=0&shareEnable=1"\/>' +

            '<param name="wmode" value="transparent"\/>' +

            '<embed ' +

            'height="255" ' +

            'width="400" ' +

            'id="uvp_fop" ' +

            'allowFullScreen="true" ' +

            'src="http://d.yimg.com/m/up/fop/embedflv/swf/fop.swf" ' +

            'type="application/x-shockwave-flash" ' +

            'flashvars="id=v' + id + '&eID=1301797&lang=us&ympsc=4195329&enableFullScreen=1&shareEnable=1"' +

            '\/>' +

            '<\/object>';

                },

    // 拼接信息显示内容,然后在append到li的底部里显示

    updateList: function (data) {

        var id,

            html = '',

            info;
        if (data.query) {

            data = data.query.results.Video;

        }

        id = data.id;

        html += '<img src="' + data.Image[0].url + '" width="50" \/>';

        html += '<h2>' + data.title + '<\/h2>';

        html += '<p>' + data.copyrightYear + ', ' + data.label + '<\/p>';

        if (data.Album) {

            html += '<p>Album: ' + data.Album.Release.title + ', ' + data.Album.Release.releaseYear + '<br \/>';

        }

        html += '<p><a class="play" href="http://new.music.yahoo.com/videos/--' + id + '">» play<\/a><\/p>';

        info = document.createElement('div');

        info.id = "info" + id;

        info.innerHTML = html;

        $('v' + id).appendChild(info);

    },

    // 获取信息并显示

    getInfo: function (id) {

        var info = $('info' + id);
        if (!info) {

            proxy.makeRequest(id, videos.updateList, videos); //执行代理职责,并传入videos.updateList回调函数

            return;

        }
        if (info.style.display === "none") {

            info.style.display = '';

        } else {

            info.style.display = 'none';

        }

    }

};

现在可以处理点击事件的代码了,由于有很多a连接,如果每个连接都绑定事件的话,显然性能会有问题,所以我们将事件绑定在<ol>元素上,然后检测点击的是否是a连接,如果是说明我们点击的是视频地址,然后就可以播放了:

$('vids').onclick = function (e) {

    var src, id;
    e = e || window.event;

    src = e.target || e.srcElement;
    // 不是连接的话就不继续处理了

    if (src.nodeName.toUpperCase() !== "A") {

        return;

    }

    //停止冒泡

    if (typeof e.preventDefault === "function") {

        e.preventDefault();

    }

    e.returnValue = false;
    id = src.href.split('--')[1];
    //如果点击的是已经生产的视频信息区域的连接play,就开始播放

    // 然后return不继续了

    if (src.className === "play") {

        src.parentNode.innerHTML = videos.getPlayer(id);

        return;

    }

        

    src.parentNode.id = "v" + id;

    videos.getInfo(id); // 这个才是第一次点击的时候显示视频信息的处理代码

};

全选反选的代码大同小异,我们就不解释了:

$('toggle-all').onclick = function (e) {
    var hrefs, i, max, id;
    hrefs = $('vids').getElementsByTagName('a');

    for (i = 0, max = hrefs.length; i < max; i += 1) {

        // 忽略play连接

        if (hrefs[i].className === "play") {

            continue;

        }

        // 忽略没有选择的项

        if (!hrefs[i].parentNode.firstChild.checked) {

            continue;

        }
        id = hrefs[i].href.split('--')[1];

        hrefs[i].parentNode.id = "v" + id;

        videos.getInfo(id);

    }

};

总结

代理模式一般适用于如下场合:

1.远程代理,也就是为了一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实,就像web service里的代理类一样。
2.虚拟代理,根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象,比如浏览器的渲染的时候先显示问题,而图片可以慢慢显示(就是通过虚拟代理代替了真实的图片,此时虚拟代理保存了真实图片的路径和尺寸。
3.安全代理,用来控制真实对象访问时的权限,一般用于对象应该有不同的访问权限。
4.智能指引,只当调用真实的对象时,代理处理另外一些事情。例如C#里的垃圾回收,使用对象的时候会有引用次数,如果对象没有引用了,GC就可以回收它了。

参考:《大话设计模式》

Javascript 相关文章推荐
增强的 JavaScript 的 trim 函数的代码
Aug 13 Javascript
使用JQuery和s3captche实现一个水果名字的验证
Aug 14 Javascript
Jquery ajaxsubmit上传图片实现代码
Nov 04 Javascript
检测input每次的输入是否合法遇到汉字输入就有问题
May 23 Javascript
javascript中的变量作用域以及变量提升详细介绍
Oct 24 Javascript
javascript结合Canvas 实现简易的圆形时钟
Mar 11 Javascript
jquery获取select选中值的方法分析
Dec 22 Javascript
bootstrap table 表格中增加下拉菜单末行出现滚动条的快速解决方法
Jan 05 Javascript
微信小程序scroll-view组件实现滚动动画
Jan 31 Javascript
使用vue-cli(vue脚手架)快速搭建项目的方法
May 21 Javascript
layui 设置table 行的高度方法
Aug 17 Javascript
基于vue和bootstrap实现简单留言板功能
May 30 Javascript
深入理解JavaScript系列(30):设计模式之外观模式详解
Mar 03 #Javascript
深入理解JavaScript系列(29):设计模式之装饰者模式详解
Mar 03 #Javascript
jQuery对象与DOM对象之间的相互转换
Mar 03 #Javascript
深入理解JavaScript系列(28):设计模式之工厂模式详解
Mar 03 #Javascript
JS运动基础框架实例分析
Mar 03 #Javascript
jQuery DOM插入节点操作指南
Mar 03 #Javascript
JS运动框架之分享侧边栏动画实例
Mar 03 #Javascript
You might like
php下使用无限生命期Session的方法
2007/03/16 PHP
php fputcsv命令 写csv文件遇到的小问题(多维数组连接符)
2011/05/24 PHP
smarty 缓存控制前的页面静态化原理
2013/03/15 PHP
php使用Header函数,PHP_AUTH_PW和PHP_AUTH_USER做用户验证
2016/05/04 PHP
PHP编写daemon process 实例详解
2016/11/13 PHP
thinkPHP自定义类实现方法详解
2016/11/30 PHP
jquery 必填项判断表单是否为空的方法
2008/09/14 Javascript
jQuery 点击图片跳转上一张或下一张功能的实现代码
2010/03/12 Javascript
只需20行代码就可以写出CSS覆盖率测试脚本
2013/04/24 Javascript
JavaScript中document对象使用详解
2015/01/06 Javascript
jquery+css3问卷答题卡翻页动画效果示例
2016/10/26 Javascript
js中动态创建json,动态为json添加属性、属性值的实例
2016/12/02 Javascript
JavaScript基本类型值-Number类型
2017/02/24 Javascript
微信小程序与php 实现微信支付的简单实例
2017/06/23 Javascript
使用MUI框架模拟手机端的下拉刷新和上拉加载功能
2017/09/04 Javascript
SelectPage v2.4 发布新增纯下拉列表和关闭分页功能
2017/09/07 Javascript
js 原生判断内容区域是否滚动到底部的实例代码
2017/11/15 Javascript
详解Vue2.0配置mint-ui踩过的那些坑
2018/04/23 Javascript
VUE+Element环境搭建与安装的方法步骤
2019/01/24 Javascript
解决layer弹出层msg的文字不显示的问题
2019/09/11 Javascript
[02:25]专访DOTA2负责人Erik 国际邀请赛暂不会离开西雅
2014/07/21 DOTA
[29:59]完美世界DOTA2联赛PWL S3 Forest vs access 第二场 12.11
2020/12/13 DOTA
python基础教程之元组操作使用详解
2014/03/25 Python
使用Python编写vim插件的简单示例
2015/04/17 Python
Swift中的协议(protocol)学习教程
2016/07/08 Python
python安装Scrapy图文教程
2017/08/14 Python
Python 比较两个数组的元素的异同方法
2017/08/17 Python
Python2和Python3之间的str处理方式导致乱码的讲解
2019/01/03 Python
详解用Python为直方图绘制拟合曲线的两种方法
2019/08/21 Python
英国领先的葡萄酒专家:Majestic Wine
2017/05/30 全球购物
英国珠宝钟表和家居礼品精品店:David Shuttle
2018/02/24 全球购物
新闻系毕业生推荐信
2013/11/16 职场文书
简历中个人自我评价分享
2014/03/15 职场文书
节水口号标语
2014/06/19 职场文书
土建专业毕业生自荐书
2014/07/04 职场文书
升学宴家长答谢词
2015/09/29 职场文书