Javascript(面向对象)——设计模式

设计模式

设计模式:针对特定问题的简洁而优雅的解决方案。这些优秀的解决方案经过了大量实际项目的验证。通俗一点说,设计模式就是给这些优秀的解决方案取个名字。

设计模式最初是静态类型语言中的设计模式,但设计模式实际上是解决某些问题的一种思想,与具体使用的语言无关。

设计模式分为三种类型,共23种。

  • 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  • 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。

在许多大型Web项目中,JavaScript代码非常多,我们绝对有必要把一些优秀的设计模式借鉴到JavaScript这门语言中。

程序设计中的主要原则:

开闭原则,即 开放扩展,关闭修改
单一职责原则,指一个类或者模块应该有且只有一个改变的原因
高内聚低耦合,内聚性又称块内联系(模块内各元素联系的紧密性),耦合性也称块间联系(模块之间的独立性)

工厂模式

工厂模式是用来创建对象的一种最常用的设计模式。
在实际开发中,工厂模式主要用于复杂的对象构建、生成多个不同的实例对象等场景。

function factory(engine,speed,color) {
    var car = {}; //原料
    car.engine = engine; //加工
    car.speed = speed; //加工
    car.color = color; //加工
    car.drive = function () {
        console.log('最高时速为:' + car.speed);
    }
    return car; //出厂
}
var car1 = factory('v4','140km/h','blue');
var car2 = factory('v6','180km/h','red');
var factory = (function (){
    var car = {
        carA: function (){
            this.type = '高配版';
            this.engine = 'v8引擎';
            this.speed = '最高时速320km/h';
        },
        carB: function (){
            this.type = '中配版';
            this.engine = 'v6引擎';
            this.speed = '最高时速220km/h';
        },
        carC: function (){
            this.type = '低配版';
            this.engine = 'v4引擎';
            this.speed = '最高时速180km/h';
        }
    }
    return function (config){
        return new car[config]();
    }
})();
var car1 = factory('carA');
var car2 = factory('carB');
console.log(car1.type);
console.log(car2.type);

单例模式

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

应用场景:对象仅需要创建一个的时候
1.windows的任务管理器
2.多线程的线程池设计
3.全局缓存
4.浏览器的window对象
5.页面中的登录弹窗,无论点击多少次,弹窗只会被创建一次

面试题
function Fun() {
    // 在函数中添加代码,使下面的运行结果成立
}
var obj1 = new Fun();
var obj2 = new Fun();
console.log(obj1 === obj2); //true
console.log(obj1.attr); // 'hello world'

最简单的单例--对象字面量

var instance = {   
 prop1: '属性1',  
 prop2: '属性2',   
 doSomething: function (){        
console.log('do Something ...');    
}}

使用闭包实现

var singleLogin = (function () {
    var instance;
    var CreateLogin = function () {
        if (instance) {
            return instance;
        }
        this.init();
        return instance = this;
    }
    CreateLogin.prototype.init = function () {
        var div = document.createElement('div');
        div.className = 'login';
        div.innerHTML = '登录窗口';
        document.body.appendChild(div);
    }
    return function () {
        return new CreateLogin();
    };
})();
console.log( singleLogin() === singleLogin() ); 

优化

var createLogin = function () { //创建对象
    var div = document.createElement('div');
    div.className = 'login';
    div.innerHTML = '登录窗口<span>X</span>';
    document.body.appendChild(div);
    return div;
}
var getSingle = function (fn) { //管理单例逻辑
    var result;
    return function () {
        // if (result) {
        //    return result;
        // }
        // return result = new fn();
        return result || ( result = new fn() );
    }
}
var singleLogin = getSingle(createLogin);
console.log( singleLogin() === singleLogin() ); //true

代理模式

代理模式:是为一个对象提供一个代理,以便控制对它的访问。
代理模式分成两个部分,一个部分是本体,即为你想要实现的功能;另一部分为代理,代理可以代替本体做一些处理。

Image.png

代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景。

Image.png
不用代理模式:
var Gift = function (n) {
    this.name = n;
};
var boy = {
    sendGift: function (target) {
        var flower = new Gift('花');
        target.receiveGift(flower);
    }
}
var girl = {
    receiveGift: function (gift) {
        console.log('收到礼物:' + gift.name);
    }
}
boy.sendGift(girl);
使用代理模式:
var Gift = function (n) {
    this.name = n;
};
var boy = {
    sendGift: function (target) {
        var flower = new Gift('花');
        target.receiveGift(flower);
    }
}
var guimi = {
    receiveGift: function (gift) {
        girl.receiveGift(gift);
    }
}
var girl = {
    receiveGift: function (gift) {
        console.log('收到礼物:' + gift.name);
    }
}
boy.sendGift(guimi);

发布-订阅模式(观察者模式)

发布-订阅模式(观察者模式)它定义对象间的一种 一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

在JavaScript开发中,我们一般用事件模型来替代传统的发布-订阅模式。最常用且最简单的发布-订阅模式:DOM事件

document.body.addEventListener('click', function () {
    alert('消息');
}, false);

订阅document.body上的click事件,当body节点被点击时,body节点便会向订阅者发布这个消息

发布-订阅模式的一个应用场景:售楼处(发布者)给意愿购房者(订阅者)发消息
var saleOffice = { //售楼处
    clientList: {}, //缓存列表,存放订阅者的回调函数
    addlisten: function (key,fn) { //添加订阅者
        if (!this.clientList[key]) { //未订阅过此类消息,创建一个缓存列表
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn); //订阅的消息添加进消息缓存列表
    },
    trigger: function (key,msg) { //发布消息方法
        var fnArr = this.clientList[key]; //取出该消息对应的回调函数集合
        if (!fnArr || fnArr.length == 0) {
            return false; // 如果未订阅该消息,则返回
        }
        for (var i = 0; i < fnArr.length; i++) {
            fnArr[i](msg); //执行所有回调函数
        }
    }
}
saleOffice.addlisten('houseTypeA',function (msg) { //订阅消息
    console.log('尊敬的客户,您关注房源信息为:' + msg);
});
saleOffice.addlisten('houseTypeB',function (msg) { //订阅消息
    console.log('尊敬的客户,您关注房源信息为:' + msg);
});
saleOffice.trigger('houseTypeA','户型A,12000/m,建筑面积90平米。'); //发布消息
saleOffice.trigger('houseTypeB','户型B,12800/m,建筑面积120平米。'); //发布消息
案例:网站登录

假如一个商城网站项目有header头部、nav导航、消息列表、购物车、地址管理等模块,这些模块的渲染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息。如果它们和用户信息模块产生了强耦合,比如下面这样的形式:

$.ajax({

    type: 'post',

    url: '[https://www.baidu.com/](https://www.baidu.com/)',

    data: 'user=xiaocuo&pass=123456',

    dataType: 'json',

    success: function (data) {

        header.setAvatar(data.avatar); // 设置头部头像

        nav.setAvatar(data.avatar); // 设置导航头像

        address.refresh(); // 刷新收货地址列表

        message.refresh(); // 刷新消息列表

        cart.refresh(); // 刷新购物车列表

        abc.refresh(data); // 刷新某某列表

        // ......

    }

})

我们必须知道header模块设置头像的方法叫setAvatar,刷新购物车列表的方法叫refresh,各种新增模块的方法...等等,我们会越来越疲于应付这些突如其来的业务要求!
用发布-订阅模式重构之后,对用户信息感兴趣的业务模块将自行订阅登录成功的消息事件

 var loginEvent = { //登录成功的消息事件

    clientList: {}, //缓存列表,存放订阅者的回调函数

    addlisten: function (key,fn) { //添加订阅者

        if (!this.clientList[key]) { //未订阅过此类消息,创建一个缓存列表

            this.clientList[key] = [];

        }

        this.clientList[key].push(fn); //订阅的消息添加进消息缓存列表

    },

    trigger: function (key,msg) { //发布消息方法

        var fnArr = this.clientList[key]; //取出该消息对应的回调函数集合

        if (!fnArr || fnArr.length == 0) {

            return false; // 如果未订阅该消息,则返回

        }

        for (var i = 0; i < fnArr.length; i++) {

            fnArr[i](msg); //执行所有回调函数

        }

    }

}

$.ajax({

    type: 'post',

    url: '[https://www.baidu.com/](https://www.baidu.com/)',

    data: 'user=xiaocuo&pass=123456',

    dataType: 'json',

    success: function (data) {

        loginEvent.trigger('loginSucc', data); // 发布登录成功消息

    }

})

各个业务模块自己监听登录成功的消息:

var header = (function () { // 头部模块

    loginEvent.addlisten('loginSucc', function (data) { //订阅登录成功的消息

        header.setAvatar(data.avatar);

    });

    return {

        setAvatar: function (data) {

            console.log('设置头部模块头像');

        }

    }

})();

var nav = (function () { // 导航模块

    loginEvent.addlisten('loginSucc', function (data) { //订阅登录成功的消息

        nav.setAvatar(data.avatar);

    });

    return {

        setAvatar: function (data) {

            console.log('设置导航模块头像');

        }

    }

})();

var address = (function () { // 地址模块

    loginEvent.addlisten('loginSucc', function (obj) { //订阅登录成功的消息

        address.refresh(obj);

    });

    return {

        refresh: function (data) {

            console.log('刷新收货地址列表');

        }

    }

})();

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