5种常见设计模式讲解(javascript篇)

简介: 5种常见设计模式讲解(javascript篇)

1.设计模式

概念

设计模式是为了解决某种问题,而设计的一套最佳解决方案。

面向对象 - 更加注重对象 - 找一种最佳的定义对象并个对象添加属性和方法 的 方案

找到的最佳的解决方案 - 就是一种设计模式

针对不同的问题,我们会有不同的最佳解决方案 - 设计模式

常见的设计模式:

  • 单例模式
  • 组合模式
  • 观察者模式
  • 命令模式
  • 代理模式
  • 工厂模式
  • 策略模式
  • 适配器模式
  • 。。。

1.1 单例模式

数据库连接 - 多个功能都需要操作数据库 - 重新连接数据库 - 1000个功能,连接1000次数据库


注册功能,每次注册都需要操作数据库 - 同一时间有1000个人在注册


mysql数据库有一个连接限制:最多只能支持同时又200个连接


我们连接一次数据库,得到一个连接,多次操作数据库,使用这一个连接其就能操作


怎么操作,让多次执行数据库语句使用同一个连接 - 我们就可以设计一个设计模式


设计模式:定义一个类,这个类new以后就得到一个连接,以后每次执行数据库语句的时候,都可以从这个类中得到一个连接

单例模式:从一个类中只能得到一个对象 - 不管操作了多少次这个类,最终得出的所有连接对象都是同一个对象


让一个类创建出来的所有对象,里面的所有属性和方法都一模一样。比如封装一个类,将一些常用的操作函数作为方法放进去,以后每次都使用同一个对象来调用这些方法正常情况,一个类创建出来的正常情况,一个类创建出来的每个对象都是不一样的。

class Carousel{
    
}
var a = new Carousel();
var b = new Carousel();
console.log(a === b); // false

单例模式就是让这两个对象是一样的,也就是说,一个类永远只有一个实例对象

var single = (function(){
    class Carousel{
       
    }
    var res = undefined;
    return function(){
        if(!res){
           res = new Carousel();
        }
        return res;
    }
})();
var s1 = single();
var s2 = single();
console.log(s1 === s2); // true

变量P暴露在全局的变量会造成全局的污染

利用闭包解决每次都会重新定义p的问题


单例模式的核心代码:

function Person(){}
function fn(Person){
    var p;
    return function(){
        if(!p){
            p = new Person;
        }
        return p;
    }
}
 
var f = fn(Person)
var p1 = f()
var p2 = f()

单例模式的应用场景在封装工具库。

例:封装封装一个工具库

单例模式 应用 封装工具库

     工具库中会有很多的方法 - 方法每次的使用 应该是同一个对象使用的,而不是应该每次都有一个新对象在使用工具
       // var t = new Tool;
        // t.setCookie('username', 'zs', 20);
        const Tool = (function () {
            class Tool {
                constructor() {
                    if (window.getComputedStyle) {
                        this.flag = true;
                    } else {
                        this.flag = false;
                    }
                }
                /获取节点属性
                getStyle(ele, attr) {
                    if (this.flag) {
                        return window.getComputedStyle(ele)[attr];
                    } else {
                        return ele.currentStyle[attr];
                    }
                }
 
                getStyle(ele, attr) {
                     尝试一段代码   不知道会不会报错
                     尝试成功 后面代码没有什么事
                    尝试失败 会报错 被cathch 捕获到  会将错误信息放到err参数里  catch{} 里可以处理这个错误 也可以不处理这个错误对上面的错误代码进行补救  错误不会再浏览器里报错
                    try {
                        return window.getComputedStyle(ele)[attr];
                    } catch (err) {
                        return ele.currentStyle[attr];
                    }
                }
                // 设置节点css属性
                setStyle(ele, styleObj) {
                    for (let attr in styleObj) {
 
                        ele.style[attr] = styleObj[attr];
                    }
                }
                // 设置cookie
                setCookie(key, value, second, path = '/') {
                    let data = new Date();
                    date.setTime(date.getTime() - 8 * 3600 * 1000 + second * 1000);
                    document.cookie = `${key}=${value};expires=${date};path=${path}`;
                }
            }
            var tool;
            return (function () {
                if (!tool) {
                    tool = new Tool();
                }
                return tool;
            })();
        })();
 

1.3 组合模式

组合模式就是制作启动器。多个类在实例化以后,执行起来使用一个同名的方法来启动,这时候可以做一个启动器,让多个类一起启动。

class Carousel{
    init(){
        console.log("轮播图开始运行");
    }
}
class Tab{
    init(){
        console.log("选项卡开始运行");
    }
}
class Enlarge{
    init(){
console.log("放大镜开始运行");
    }
}
// 这3个类要运行起来需要各自实例化,并调用每个类中的init方法,此时就可以使用组合模式做一个启动器

组合模式制作启动器:

class Starter{
    constructor(){
this.arr = []; // 定义一个数组
    }
    add(className){
        this.arr.push(className); // 将这个多个类放进数组
    }
    run(){
        for(var i=0;i<this.arr.length;i++){
            arr[i].init(); // 让数组中的每个类都调用init方法
        }
    }
}
var starts = new Starter();
starts.add(new Carousel);
starts.add(new Tab);
starts.add(new Enlarge);
starts.run();

1.4 发布订阅模式

https://blog.csdn.net/weixin_44070254/article/details/117574565?spm=1001.2014.3001.5501

有人订阅 有人发布

发布订阅模式:需要一个观察者 需要一个被观察者 如果观察者发现被观察者的状态发生了改变,就需要执行一个任务

定义一个观察者:

class Observer{
    // 观察者有名字和任务
    constructor(name,task){
        this.name = name
        this.task = task
    }
}
// 定义班主任
var bzr = new Observer('班主任',function(state){
    console.log("因为"+state+",所以"+this.name+"罚站");
})
// 定义了一个年级主任
var zr = new Observer('年级主任',function(state){
    console.log("因为"+state+",所以"+this.name+"要找班主任");
})
 
// 被观察者
class Subject{
    // 有自己的状态
    constructor(state){
        this.state = state
        // 观察者们
        this.observer = [];
    }
    // 添加被观察者
    add(...arr){
        this.observer = this.observer.concat(arr)
    }
    // 改变状态
    changeSate(state){
        this.state = state
        // 触发观察者的任务
        for(let i=0;i<this.observer.length;i++){
            this.observer[i].task(state)
        }
    }
}
var xm = new Subject('学习')
xm.add(bzr,zr)
// xm.changeSate('摸了一下小红的手')
 
xm.changeSate('玩游戏')

观察者模式,又称发布-订阅模式。意思是让一个人不停的监控某件某件东西,当这个东西要发生某种行为的时候,这个人就通知一个函数执行这个行为的操作。

例:当事件的代码写好以后,其实这个事件就不停的监控用户在页面中的行为,一旦用户触发这个事件的时候,就调用函数处理这个事件。

div.addEventListener("click",function(){});
// 这个事件写好以后,就一直在页面中监控用户行为,用户点击这个元素的时候,就调用函数

观察者模式就是类似的操作,写观察者模式的目的,是为了给一个非元素的数据绑定一个自定义事件。


例:给一个obj绑定一个abc的事件


分析:


给一个元素绑定事件,有绑定方法,有触发条件,有取消绑定。


要给一个对象绑定一个自定义事件。那么这个事件如何绑定,如何触发,如何解绑这个事件。


所以:

  • 需要一个方法处理事件的绑定。
  • 需要一个方法处理如何触发这个事件。
  • 需要一个方法处理如何解绑这个事件。

元素的事件,一个事件类型可以绑定多个处理函数。

对象的自定义事件如何让一个事件类型绑定多个函数。

所以:

需要一个空间,存储事件类型对应的处理函数们。

1.4.1 雏形:
class watch{
    bind(){
        
    }
    touch(){
        
    }
    unbind(){
        
    }
}
var w = new watch();

此时,如要给这个对象绑定事件和处理函数的话,需要事件类型和处理函数作为参数,所以调用时要传入实参

var w = new watch();
w.bind("cl",a); // 给w对象绑定cl事件类型,执行a函数
w.bind("cl",b); // 给w对象绑定cl事件类型,执行b函数
function a(){
    console.log("事件处理函数a");
}
function b(){
    console.log("事件处理函数b");
}
1.4.2 绑定

在bind方法中接收参数,并将事件类型和处理函数对应存储起来:

constructor(){
    this.obj = {} // 存储格式:{事件类型:[函数1,函数2]}
}
bind(type,handle){
    this.obj[type] = [handle]; // 对象存储方式{"cl":[a]}
}

如果给这个事件再绑定一个函数b的话,会将原来的a覆盖掉,所以,应该先判断,如果对应的这个数组中没有数据就直接放进去,如果有了就应该追加

bind(type,handle){
    if(!this.obj[type]){
        this.obj[type] = [handle]; // 对象存储方式{"cl":[a]}
    }else{
        this.obj[type].push(handle);
    }  
}

打印w对象的结果:

绑定后的存储结果

1.4.3 触发

触发这个事件需要传入触发哪个事件类型

touch(type){
    // 首先要判断,这个事件类型是否绑定,没有绑定不能触发
    if(!this.obj[type]){
        return false;
    }else{
        // 将这个事件的所有绑定的处理函数一起调用
        for(var i=0;i<this.obj[type].length;i++){
            this.obj[type][i]();
        }
    }
}

测试:

触发测试

这两个处理函数都没有参数,如果要传入参数的时候该怎么处理?

触发事件的时候就要传入实参

w.touch("cl","张三",20);

触发事件的方法就应该处理这些参数

touch(type,...arr){ // 因为参数不定长,所以使用合并运算符
    // 首先要判断,这个事件类型是否绑定,没有绑定不能触发
    if(!this.obj[type]){
        return false;
    }else{
        // 处理参数:模拟系统事件的参数事件对象,将所有参数都集中在一个对象中
        var e = {
            type:type,
            args:arr
        }
        // 将这个事件的所有绑定的处理函数一起调用
        for(var i=0;i<this.obj[type].length;i++){
            this.obj[type][i](e);
        }
    }
}

添加一个带参数的处理函数,并触发事件执行:

w.bind("cl",c); // 给w对象绑定cl事件类型,执行c函数
w.touch("cl","张三",20);
function c(e){
    console.log("我是处理函数c,打印:姓名"+e.name+",年龄"+e.age);
}

结果:

带参数的处理函数处理结果

1.4.5 解绑

解绑也需要知道解绑的事件类型和处理函数

unbind(type,handle){
    // 先判断是否绑定了这个事件
    if(!this.obj[type]){
        return false;
    }else{
        // 从数组中将这个处理函数删除
        for(var i=0;i<this.obj[type].length;i++){
            if(this.obj[type][i] === type){
                this.obj[type].splice(i,1);
                i--; // 放置数组塌陷
            }
        }
    }
}

解绑测试:

解绑测试结果

如果绑定事件的时候使用的匿名函数,就无法进行解绑了,所以再添加一个解绑事件所有处理函数的方法:

clear(type){
    if(!this.obj[type]){
        return false;
    }else{
        // 直接从对象中将这个属性删除
        delete this.obj[type];
    } 
}

1.5 观察者模式

观察者模式跟发布订阅模式不一样的地方在于,要有观察者和被观察者。

创建观察者:

// 创建观察者
class Observer{
    // 观察者有姓名和技能 - 技能是一个函数
    constructor(name,skill){
        this.name = name;
        this.skill = skill
    }
}
// 创建观察者
var bzr = new Observer('班主任',function(state){
    console.log('因为'+state+'叫家长')
})
var xz = new Observer('校长',function(state){
    console.log('因为'+state+'叫班主任')
})
console.log(bzr,xz)


创建被观察者:

// 创建被观察者
    class Subject{
        // 被观察者有状态 和 观察者列表
        constructor(state){
            this.state = state
            this.observers = []
        }
        // 添加观察者
        addObserver(observer){
            var index = this.observers.findIndex(v=>v === observer)
            if(index<0){
                this.observers.push(observer)
            }
        }
        // 改变状态
        setState(val){
            if(val!==this.state){
                this.state = val;
                this.observers.forEach(v=>{
                    v.skill(this.state)
                })
            }
        }
        // 删除观察者
        delObserver(observer){
            var index = this.observers.findIndex(v=>v === observer)
            if(index>=0){
                this.observers.splice(index,1)
            }
        }
    }
    // 创建被观察者
    var xm = new Subject('学习')
    // 添加观察者
    xm.addObserver(bzr)
    xm.addObserver(xz)
    xm.addObserver(bzr)
    console.log(xm)
    // 改变状态
    xm.setState('玩游戏')

1.6 策略模式

一个问题有多种解决方案,且随时还可以有更多的解决方案,就是策略模式。例如:一个商品在售卖的时候,会有多种折扣方式,慢100减10元,满200减30元等…


商品活动:

满100减10

满200减30

满300打8折


策略模式:将折扣放在闭包中保存起来 - 通过闭包对折扣进行增删改查操作正常的函数表达方式:

function fn(type,price){
    if(type === '100_10'){
       return price -= 10
    }else if(type === '200_30'){
       return price -= 0
    }else{
        return '没有这种折扣'
    }
}
 
fn('100_10',160)

这种方式,在添加折扣或删除折扣的时候总是需要修改原函数。

策略模式代码:

var calcPrice = (function(){
    // 以闭包的形式存储折扣数据
    var sale = {
        '100_10':function(price){
            return price -= 10
        },
        '200_20':function(price){
            return price -= 20
        }
    }
    function calcPrice(type,price){
        // 闭包中使用折扣
        if(type in sale){
            return sale[type](price)
        }else{
            return '没有这个折扣'
        }
    }
    // 折扣函数添加属性 - 添加折扣
    calcPrice.add = function(type,fn){
        if(type in sale){
            console.log('折扣存在')
        }else{
            sale[type] = fn
        }
    }
    // 删除折扣
    calcPrice.del = function(type){
        if(type in sale){
            delete sale[type]
        }else{
            console.log('折扣不存在')
        }
    }
    return calcPrice
})()
 
var p = calcPrice('100_10',200)
calcPrice.add('200_320',function(price){
    return price -= 50
})
calcPrice.del('200_330')
var p = calcPrice('200_320',200)
console.log(p)
相关文章
|
4月前
|
设计模式 JavaScript 前端开发
JavaScript进阶 - JavaScript设计模式
【7月更文挑战第7天】在软件工程中,设计模式是解决常见问题的标准解决方案。JavaScript中的工厂模式用于对象创建,但过度使用可能导致抽象过度和缺乏灵活性。单例模式确保唯一实例,但应注意避免全局状态和过度使用。观察者模式实现了一对多依赖,需警惕性能影响和循环依赖。通过理解模式的优缺点,能提升代码质量。例如,工厂模式通过`createShape`函数动态创建对象;单例模式用闭包保证唯一实例;观察者模式让主题对象通知多个观察者。设计模式的恰当运用能增强代码可维护性。
81 0
|
设计模式 JavaScript 前端开发
常见 JavaScript 设计模式 — 原来这么简单(一)
常见 JavaScript 设计模式 — 原来这么简单
116 0
|
设计模式 存储 JavaScript
常见 JavaScript 设计模式 — 原来这么简单(三)
常见 JavaScript 设计模式 — 原来这么简单
61 0
|
设计模式 前端开发 JavaScript
常见 JavaScript 设计模式 — 原来这么简单(二)
常见 JavaScript 设计模式 — 原来这么简单
100 0
|
设计模式 算法 JavaScript
Javascript设计模式之策略模式
Javascript设计模式之策略模式
104 0
|
设计模式 JavaScript 前端开发
JavaScript设计模式-工厂设计模式(1)
JavaScript设计模式-工厂设计模式(1)
|
设计模式 JavaScript 前端开发
JavaScript设计模式-策略模式(10)
JavaScript设计模式-策略模式(10)
|
设计模式 存储 JavaScript
23种JavaScript设计模式
23种JavaScript设计模式
90 0
|
设计模式 算法 JavaScript
JavaScript中的设计模式-策略模式
设计模式在我们编程中是十分重要的! 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
|
设计模式 JavaScript 前端开发
模式(一)javascript设计模式
模式(一)javascript设计模式