• 前端开发之观察者模式


    什么是观察者模式

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

    后记

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

  • 相关阅读:
    1467E. Distinctive Roots in a Tree(可持久化线段树+树上差分)
    1473E. Minimum Path(最短路+三维DP)
    LeetCode88. 合并两个有序数组
    LeetCode75. 颜色分类
    LeetCode80. 删除排序数组中的重复项 II
    定义企业结构-后勤配置
    查看清账凭证
    表T043S中,XXXX输入丢失
    过账,未清项管理,银行中转科目
    PK码
  • 原文地址:https://www.cnblogs.com/leavenup/p/4581678.html
Copyright © 2020-2023  润新知