zoukankan      html  css  js  c++  java
  • 前端开发之观察者模式

    什么是观察者模式

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

    后记

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

  • 相关阅读:
    左偏树
    论在Windows下远程连接Ubuntu
    ZOJ 3711 Give Me Your Hand
    SGU 495. Kids and Prizes
    POJ 2151 Check the difficulty of problems
    CodeForces 148D. Bag of mice
    HDU 3631 Shortest Path
    HDU 1869 六度分离
    HDU 2544 最短路
    HDU 3584 Cube
  • 原文地址:https://www.cnblogs.com/leavenup/p/4581678.html
Copyright © 2011-2022 走看看