前端开发之观察者模式

什么是观察者模式

观察者模式(有时又被称为发布-订阅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框架中的观察者模式的应用。

后记

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

原文地址:https://www.cnblogs.com/leavenup/p/4581678.html