美文网首页
前端设计模式(二)

前端设计模式(二)

作者: JackfengGG | 来源:发表于2021-03-08 10:17 被阅读0次

    前言
    上期我们学了工厂模式,单例模式,观察者模式,发布订阅模式,策略模式。
    让我们继续学习吧。

    代理模式 Proxy Pattern

    代理模式是为一个对象提供一个代用品,以便控制对它的访问。
    人话:购买者与卖房者中间的那个中介。
    前端实现: 当调用一个方法Fn时,中间加入代理方法Proxy。先调用Proxy方法,Proxy内部再决定如何调用Fn方法。
    举个例子:在房产销售中,有买房,卖方。如果买房一只去Call卖方。那卖方就将被烦死,所以需要中介的介入。

    
    var Buyer = function (name){
        this.name = name
    }
    Buyer.prototype.pay = function (money){
        zhongjie.ask(money)
    }
    
    var zhongjie = {
        ask(money){
            if(money<100){
                console.log("Sorry,钱不够不卖")
            }else{
                Seller.sell()
            }
        }
    }
    
    var Seller = {
        sell:function(){
            console.log("Ok,成交了");
        }
    }
    
    var buyer1 = new Buyer("买家一");
    buyer1.pay(50);
    //log: Sorry,钱不够不卖
    
    var buyer2 = new Buyer("买家二");
    buyer1.pay(100);
    //log: Ok,成交了
    
    

    说明
    像这种只有达成一定条件再去call真实方法的代理就叫做保护代理。
    那这类代理在真实项目中可以做什么呢?
    举个例子。
    网站js错误收集
    需求:当网页有任何Javascript错误时候就上传到服务器日志。

    var uploadError = function (errorOrArray){
        //ajax上传
        //ajax(errorOrArray)
    }
    
    window.onerror = function (e){
        uploadError(e);
    }
    
    

    是不是完成了?
    嗯。
    那么问题来了。
    每一个error都要上传吗?我们凑满十个error再上传吧。减少请求,加入代理吧。

    var uploadError = function (errorOrArray){
        //ajax上传
        //ajax(errorOrArray)
    }
    
    var uploadQueue = [];
    var proxyUploadError = function (error){
        uploadQueue.push(error);
    
        if(uploadQueue.length >=10){
            uploadError(uploadQueue);
            uploadQueue = [];
        }
    }
    
    window.onerror = function (e){
        proxyUploadError(e);
    }
    

    是不是完成了?
    嗯。
    那么问题来了。
    如果同时有大量error产生。比如某项目的scroll事件产生了几十万个error(true story)
    凑满十个还是不够看呀。
    我们换个策略吧。加入节流,并且忽略相同的报错。

    var uploadError = function (errorOrArray){
        //ajax上传
        //ajax(errorOrArray)
        console.log("上传成功")
    }
    
    
    var proxyUploadError = (function (error){
        var errorQuene = [],
        _uploadError = _.throttle(function (){
            uploadError(errorQuene);
            errorQuene = [];
        }, 10000);//最多十秒上传一次
    
        return function (error){
            if(error重复){
                return;
            };
            errorQuene.push(error);
            _uploadError();
        }
    })
    
    window.onerror = function (e){
        proxyUploadError(e);
    }
    

    思考
    Ok,一个简单的错误收集上传功能就完成了
    那同学可能会问,为什么不把逻辑直接写入uploadError而要proxyUploadError呢?
    1.职能单一,这样才可以很好的复用
    2.在有些场景下。可以有多个Proxy存在,至于用哪个还是具体看情况。

    其实这个收集器还缺个逻辑,想的出来吗?

    职责链模式 Chain of Responsibility Pattern

    职责链模式为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
    在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
    人话:击鼓传花,最后一个人执行。
    前端实现:不断回调函数直到最终终止
    举个例子:审批链就是一种很好的实现

    class Action {
        constructor(name) {
            this.name = name;
            this.nextAction = null;
        }
        setNextAction(action) {
            this.nextAction = action;
        }
        handle() {
            console.log(`${this.name} 测试通过`);
            if (this.nextAction != null) {
                this.nextAction.handle();
            }
        }
    }
    
    let a1 = new Action('DEV');
    let a2 = new Action('TEST');
    let a3 = new Action('UAT');
    a1.setNextAction(a2);
    a2.setNextAction(a3);
    a1.handle();
    
    //DEV 测试通过
    //TEST 测试通过
    //UAT 测试通过
    

    说明
    类似职责链模式的实现还有dom/组件中的父子节点事件不断的回调。

    装饰器模式 Decorator Pattern

    装饰器模式允许向一个现有的对象动态添加新的功能,同时又不改变其结构。继承来给对象增加功能来说,装饰器模式相比生成子类更为灵活优雅。
    白话:想要什么直接拿来用就好了。
    先让我们看看是如何实现的。

    function Car() {
        console.log("我是一辆普通车");
    }
    
    Car.prototype = {
        drive: function () {
            console.log('我能跑60码');
        },
    }
    
    //创建装饰器
    var Decorator = function (car) {
        this.car = car;
    }
    // 装饰者要实现这些相同的方法
    Decorator.prototype = {
        drive: function () {
            this.car.toString();
        },
    }
    
    var Porsche = function (car){
        Decorator.call(this, car);
        console.log("升级成保时捷");
    }
    Porsche.prototype = new Decorator();
    Porsche.prototype.drive = function (){
        console.log('我能跑200码');
    }
    
    var Turbo = function (car){
        Decorator.call(this, car);
        console.log("装上涡轮增鸭");
    }
    Turbo.prototype = new Decorator();
    Turbo.prototype.drive = function (){
        console.log('我能跑500码');
    }
    
    var car = new Car();//我是一辆普通车
    car.drive();//我能跑60码
    var porsche = new Porsche(car);//升级成保时捷
    porsche.drive();//我能跑200码
    var turboCar = new Turbo(porsche);//装上涡轮增鸭
    turboCar.drive();//我能跑500码
    
    

    在es7之后只需

    @Porsche
    @Turbo
    class Car{
        drive(){}
    }
    
    

    说明
    当然现在不必大费周折再这么写,但装饰器思想还是一样的。

    外观模式 Facade Pattern

    外观模式为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层借口,这个接口使得这一子系统更加容易使用。
    白话:一个方法实现多个功能/适配单一功能。
    前端实现:在一个方法里面利用多个判断进行操作。

    function Sizzle( selector, context, results, seed ){
        var m, i, elem, nid, match, groups, newSelector,
            newContext = context && context.ownerDocument,
            //上下文默认为document,节点类型默认为9
            nodeType = context ? context.nodeType : 9;
        results = results || [];
    
        // 对选择器值为非字符串、为假、节点类型不符合要求时的返回值进行处理
        if ( typeof selector !== "string" || !selector ||
            nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
    
            return results;
        }
    
        // 操作HTML文档
        if ( !seed ) {
    
            if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
                setDocument( context );
            }
            context = context || document;
    
            if ( documentIsHTML ) {
    
                if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
    
                    // ID 选择器
                    if ( (m = match[1]) ) {
    
                        // 文档内容
                        if ( nodeType === 9 ) {
                            if ( (elem = context.getElementById( m )) ) {
    
                                if ( elem.id === m ) {
                                    results.push( elem );
                                    return results;
                                }
                            } else {
                                return results;
                            }
    
                        // 元素内容 
                        } else {
    
                            if ( newContext && (elem = newContext.getElementById( m )) &&
                                contains( context, elem ) &&
                                elem.id === m ) {
    
                                results.push( elem );
                                return results;
                            }
                        }
    
                    // 类型选择器
                    } else if ( match[2] ) {
                        push.apply( results, context.getElementsByTagName( selector ) );
                        return results;
    
                    // Class 选择器
                    } else if ( (m = match[3]) && support.getElementsByClassName &&
                        context.getElementsByClassName ) {
    
                        push.apply( results, context.getElementsByClassName( m ) );
                        return results;
                    }
                }
    
                ...
            }
        }
    
        // 返回 调用select()方法后的值
        return select( selector.replace( rtrim, "$1" ), context, results, seed );
    }
    
    

    说明
    可以看下jQuery的 Sizzle选择器
    通过一层层的判断将3个功能合为一。
    getElementById
    getElementsByTagName
    getElementsByClassName

    当然这种做法早已过时,但jQuery在当年还是很辉煌的。

    总结

    设计模式总共有23种,适合前端的基本上就这几种。
    灵活运用起来,让我们的代码看起来更优雅。

    相关文章

      网友评论

          本文标题:前端设计模式(二)

          本文链接:https://www.haomeiwen.com/subject/ctkpfltx.html