前端设计模式(二)

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容