前端设计模式(二)

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

代理模式 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种,适合前端的基本上就这几种。
灵活运用起来,让我们的代码看起来更优雅。

你可能感兴趣的:(前端设计模式(二))