前端开发之观察者模式

摘要:
什么是观察者模式?观察者模式(有时称为发布-订阅>PM)是一种绑定。百度百科中有一个关于观察者模式应用场景的词。我们注意到,最常见的实现使用事件委派函数openAPP(event){event.prpreventDefault()。
什么是观察者模式

观察者模式(有时又被称为发布-订阅Subscribe>模式、模型-视图View>模式、源-收听者Listener>模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。(来源于百度百科

还是不懂,没关系

Alt text

今天晚上老大要上线一个活动,这时候PM来通知你了, 
“XX,今天有个活动需要你支持一下,巴拉巴拉,上线了发个邮件给我,我先回家了。” 
“尼玛,F**K!” 
但是没办法,这个事件已经跟你绑定了,只有完成条件才能解绑,于是你疯狂code,终于上完了线,发了邮件通报,这个时候这个事件才算完成。

这个栗子里面,PM就是一个订阅者(bind),向你订阅了一个邮件通报,他可以不用关心你的code过程,只需要获得通知即可。你就是一个发布者,在满足上线这个要求后,才触发了发邮件的操作(trigger)。

观察者模式适用场景

百度百科里面有个单词大家不造有没有注意到,事件处理,而javascript就是纯事件驱动的语言。为什么说事件驱动适合用观察者模式解决,分析一下事件产生的要素你就知道,一个事件必须包含事件时间事件对象事件起因事件经过事件结果。 
什么!你要把这些全跟我说! 
Alt text

对于一个日理万机的攻城狮来说,你TM只要告诉我事件结果就好了,我只需要知道这个事情已经发生即可。

前端之观察者模式应用

PM:在每个播放的链接上加个调起逻辑

ok,so easy,最常见的实现使用事件委托

    function openAPP(event) {
        event.preventDefault();
        //open app
        ......
    }
    $('body').on('click', '[data-openapp=true]', openAPP);

然而事情没有那么简单:

PM:现在登录的用户直接播放,未登录的用户弹登录框 
Coder:OK!
 
如果你的登录接口是个异步请求,你可能会这样做:

function login() {
    $.ajax({
        url: '/login',
        success: function(data) {
            if(data.login === true) {
                window.login = true;
            }
        }
    });
}
$('body').on('click', '[data-openapp=true]', function(event) {
    event.preventDefault();
    if(typeof window.login !== 'undefined'
        && window.login === true) {
        location.href = $(this).data('openapp-url');
    }else {
        //show dialog
        ......
    }
}

你以为事情完了吗?NO

PM:已登录的用户不用展示首屏广告了,另外送个五块钱券吧! 
Coder:WTF!
 
没办法,改吧。

        function showADs() {
            if(typeof window.login !== 'undefined'
                && window.login === true) {
                return;
            }else {
                //show ads
                ......
            }
        }
        //送券部分省略

然而以上代码只适用于模板登录情况,如果登录接口是异步接口呢,那只能去login回调函数里面加一个去除广告的逻辑了。事情做到这,你有没有感觉到一丝丝egg hurt,如果PM再加这样的逻辑肿么办?有没有办法做到登录之后就发出通知呢,所有的跟登录相关的事件都绑定到这个通知上去?

仔细分析浏览器事件绑定的原理,我们可以发现,案列一中那个最常见的dom事件绑定代码,其实内部实现原理是这样子的,浏览器为每个节点维护一个eventMap,类似于

    eventMap = {
        'click' : [
            fn1,
            fn2
        ],
        'touchstart' : [
            fn3,
            fn4
        ]
    }

当浏览器USER Interface线程捕获到用户的click操作时,浏览器就去eventMap里面查找索引为click数据,发现里面有fn1fn2,并依次执行。

在上面的代码中,我们只是向浏览器订阅了一个click事件,事件在什么时候、什么时间触发全都由浏览器内部实现。在发生click的时候你可以执行fn1fn2、….fnn。这个不就是我们所需要解决的事件耦合的问题嘛!事情到这,就很简单了,我们只需要实现一个自定义事件队列即可。 
参考code,来自alloyteam

    Events = function() {

       var listen, log, obj, one, remove, trigger, __this;

       obj = {};

       __this = this;

       listen = function( key, eventfn ) {  

         var stack, _ref;  

         stack = ( _ref = obj[key] ) != null ? _ref : obj[ key ] = [];

         return stack.push( eventfn );

       };

       one = function( key, eventfn ) {

         remove( key );

         return listen( key, eventfn );

       };

       remove = function( key ) {

         var _ref;

         return ( _ref = obj[key] ) != null ? _ref.length = 0 : void 0;

       };

       trigger = function() {  

         var fn, stack, _i, _len, _ref, key;

         key = Array.prototype.shift.call( arguments ); 

         stack = ( _ref = obj[ key ] ) != null ? _ref : obj[ key ] = [];

         for ( _i = 0, _len = stack.length; _i < _len; _i++ ) {

           fn = stack[ _i ];

           if ( fn.apply( __this,  arguments ) === false) {

             return false;

           }

         }

         return {

            listen: listen,

            one: one,

            remove: remove,

            trigger: trigger

         }

       }

现在再来改我们之前的代码

    var eventCenter = new Events();
    eventCenter.listen('login', function(data) {
        //隐藏广告
        hideADs(arguments);
    });
    eventCenter.listen('login', function(data) {
        //登录播放跳转
        openUrl(arguments);
    });
    eventCenter.listen('login', function(data) {
        //赠送代金券
        sendCash(arguments);
    });
    function login() {
        $.ajax({
            url: '/login',
            success: function(data) {
                if(data.login === true) {
                    eventCenter.trigger('login');
                }
            }
        });
    }

完成!

总结与展望

因为观察者模式应用之广,本来想发散出去讲的,浏览器中的观察者模式前后端数据交互中的观察者模式,发现涉及的内容太多,根本停不下来,所以先大致对观察者模式做个简单介绍,后续想到再写,让大家了解观察者模式的两个特性,为解耦而生为事件而生。下一篇博客里我打算讲讲当前前端比较火的backboneangularjs以及一些MVP框架中的观察者模式的应用。

后记

因为之前一段时间比较忙以及很久都没写过博客,前端公众号虽然筹备了很久但是一直没去写,相信万事开头难,千里之行始于足下。如有建议或者意见,欢迎反馈

免责声明:文章转载自《前端开发之观察者模式》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇在水晶报表中添加转换金额大写功能Android Paint的使用以及方法介绍(附源码下载)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

UniAPP 利用sqlite保存数据

背景:利用uniapp开发一个APP,APP需要在断网的情况下,临时保存数据,把数据保存在uniapp的sqlite里面,这样可以随时的取到所保持的数据。 1.在uniapp的工程中需要添加sqlite数据库,如下图   2.在uniapp的共同组件中添加下面文件。 function openComDB(name, path, callback) {...

js动态设置根元素的rem方案

方案需求: rem 单位在做移动端的h5开发的时候是最经常使用的单位。为解决自适应的问题,我们需要动态的给文档的根节点添加font-size 值。 使用mediaquery 可以解决这个问题,但是每一个文件都引用一大串的font-size 值很繁琐,而且值也不能达到连续的效果。 就使用js动态计算给文档的fopnt-size 动态赋值解决问题。 设计稿以7...

数据库-求候选关键字

这类题目都是给定关系模型,求候选关键字.  题型: 这种给定关系模式和函数依赖的题目 ,做法大致有三步: 1、根据关系模式和函数依赖画出有向图. 2、找出是否有入度为0(即是没有任何元素可以推出他的元素),然后尝试是否可以从这个元素开始,随着箭头来遍历这个图,看看是否是哪一个元素都能遍历的到, 如果能够遍历的到的话,就可以判断这个关系的候选关键字为这个...

ThreeJS读取GeoJson文件,绘制地图板

从网上大神那儿找来的代码,稍微修改了一下,ThreeJS感觉好难用,文档写的太简单了,不好下手 <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>3D</title> &l...

宋浩《概率论与数理统计》笔记---1..1.1-1.1.3、概率论基本概念

宋浩《概率论与数理统计》笔记---1..1.1-1.1.3、概率论基本概念 一、总结 一句话总结: 1、随机试验 条件? 1、在相同条件下可重复 2、结果不止一个 3、无法预测 4、用字母E表示 2、事件、随机事件、基本事件、复合事件 分别是什么? 事件:每次随机试验的结果 随机事件:随机的事件,通常用大写字母ABC等来表示 基本事件:要看实验目的:相对于...

Docker中提交任务到Spark集群

1.  背景描述和需求 数据分析程序部署在Docker中,有一些分析计算需要使用Spark计算,需要把任务提交到Spark集群计算。 接收程序部署在Docker中,主机不在Hadoop集群上。与Spark集群网络互通。 需求如下 1、在Docker中可程序化向Spark集群提交任务 2、在Docker中可对Spark任务管理,状态查询和结束 2.  解决方...