当前位置: 首页>后端>正文

js-设计模式学习和理解

1.单例模式

js-设计模式学习和理解,第1张

理解: 一个对象只有一个实例,无论初始化了几次,都是对一个实例的调用。(比如全局的弹窗组件)

原理: 利用闭包存储实例, 实例化的时候判断局部变量是否实例化,实例化则直接返回,未实例化则实例化并返回。

实例:

let SingleModel = (function () {
    let innerm = null
    function Sigle (name) {
        this.name = name
    }
    return function inner(name) {
        if (!innerm) {
            innerm = new Sigle(name) 
        }
        return innerm
    }
})()
let newObj1 = new SingleModel('asdfasdf ')
let newObj2 = new SingleModel('这里不新建实例')

console.log(newObj1 === newObj2, newObj1.name, newObj2.name) // true 'asdfasdf ' 'asdfasdf '

es6 class方法实现

static 方法类似于window方法,不可被继承,只能通过类名加方法名调用

// es6 class方式实现
class SingleModel{
    constructor (name) {
      this.name = name
    }
    static sign (name) {
      if (!this.tmpboj) {
        this.tmpboj = new SingleModel(name)
      }
      return this.tmpboj
    }
}
let o1 = SingleModel.sign('abai')
let o2 = SingleModel.sign('aheiya ')
console.log(o1.name, o2.name, o1 === o2) // abai abai true
// 单例模式,使用 即时函数 的闭包形式保障单例引用不被影响
 (function (w) {
     var instance = null
     function Tool () {
         if (instance) { return instance }
         instance = this
         this.name = 'abai'
         this.info = 'test'
     }
     w.Tool = Tool
 })(window)

 var t1 = new Tool()
 console.log(t1) // Tool {name: 'abai', info: 'test'}
 var t2 = new Tool()
 console.log(t2) // Tool {name: 'abai', info: 'test'}
 console.log(t1 === t2) //  true
 t1.name = '暗黑啊'
 console.log(t2) // Tool {name: '暗黑啊', info: 'test'}  虽然即时函数可以避免单例工厂依赖变量不受影响,但是还是可以通过实例来修改依赖变量,所以要注意这一点# 一级标题

惰性函数

一种单例模式的实现方案
function Tool () {
    var instance = this
    this.name = 'abai'
    this.info = 'test'
    Tool = function () {return instance }
}
// 惰性函数 原型的操作,必须再实例化之前,否则无法操作原型,因为Tool指???已经变化,instance 的原型是无法操作的。改进如下
function Tool () {
    var lastPrototype = Tool.prototype
    
    this.name = 'abai'
    this.info = 'test'

    var instance = this
    Tool = function () {return instance }
    Tool.prototype = lastPrototype
    // instance = new Tool() // 返回自己,不知道大神的源码上为什么有这一行,注释了也是可行的
    instance.constructor = Tool
    
    return instance
}

let t1 = new Tool()
console.log(t1) // Tool {name: 'abai', info: 'test', constructor: ?}
Tool.prototype.run = () => {console.log('跑')}
let t2 = new Tool()
console.log(t2, t1 === t2) // Tool {name: 'abai', info: 'test', constructor: ?} true
t1.run() // 跑
t2.run() // 跑

工厂模式

> 应用: 比如需要一个复杂的对象,但是使用的时候有不需要关心创建过程,就可以先创建一个工厂,用来生产复杂对象。
js-设计模式学习和理解,第2张

1.简单工厂模式:

function makeCoffee(dou, water) {
        // 咖啡机,生产的对象就是咖啡
        // 这个工厂只生产咖啡,只是浓度不同而已,这就是【简单的工厂模式】,小厂子
        var obj = {}
        obj.dou = dou
        obj.water = water
        obj.bili = dou / (water + dou)
        return obj
    }

    var coffee = makeCoffee(10, 5)
    console.log(coffee) // {dou: 10, water: 5, bili: 0.6666666666666666}

2.复杂工厂模式

function MakeJuce (type, meta) { // type 材料类型, meta,材料数量
        // 果汁厂, 可以生产 苹果汁,香蕉汁,橘子汁
        // 复杂工厂模式,多条生产线,可拓展,可生产不同产品
        this.type = type
        this.meta = meta
        this.desc = ''
        this.make(type, meta)
    }
    MakeJuce.prototype.make = function (type, meta) {
        if (typeof this[type] == 'function') {
            this[type](meta)
        } else {
            throw `抱歉,无法制造${type}果汁`
        }
    }
    MakeJuce.prototype.extend = function (obj) {
        for (let key in obj) {
            this[key] = obj[key]
        }
    }
    MakeJuce.prototype.extend({ // 通过原型对象来调用方法,拓展生产线(原型对象是对象不是类,可以找到this指向,所以可以执行)
        Apple (n) { 
            let m = n *5
            this.desc = `您造了${m}杯苹果汁`
        },
        Pear (n) {
            let m = n + 6
            this.desc = `您造了${m}杯鲜梨汁`
        }
    })

    var maker = new MakeJuce('Apple', 18)
    console.log(maker) // MakeJuce {type: 'Apple', meta: 18, desc: '您造了90杯苹果汁'}

    var maker1 = new MakeJuce('Pear', 25)
    console.log(maker1) // MakeJuce {type: 'Pear', meta: 25, desc: '您造了31杯鲜梨汁'}

    var maker2 = new MakeJuce()
    console.log(maker2) // Uncaught 抱歉,无法制造undefined果汁

观察者模式

观察者模式通常都是后端用,前端用的不多。


js-设计模式学习和理解,第3张
/*
    构成
    1.消息发布者
    2.可以让别人订阅你的消息
    3.能够发布消息
*/
var lk = {
    users: {},
    addUser (type, data, callback) {
        if (typeof this.users[type] === 'undefined') {
            this.users[type] = []
        }
        this.users[type].push({...data, callback})
    },
    publishMsg(type, msg) {
        this.users[type].forEach(item => {
            console.log(`${type}的${item.name}收到消息: ${msg}`)
            item.callback.apply(item, [msg])
        })
    }
}

let user1 = {name: '张三', age: 18}
let user2 = {name: '李四', age: 29}
let user3 = {name: '王麻子', age: 79}

function handleCall (str) {
    console.log(this)
}
lk.addUser('h5', user1, handleCall)
lk.addUser('h5', user2, handleCall)
lk.addUser('h5', user3, handleCall)
lk.addUser('php', user1, handleCall)
lk.addUser('php', user3, handleCall)
lk.addUser('python', user2, handleCall)


lk.publishMsg('h5', 'h5今晚不上课')
lk.publishMsg('php', 'php明天放假一天')

备忘录模式

对函数 的??????结果缓存,用的时候看下是否存在。存在则直接返回,不存??则计算。
主要用于一些比较耗费性能的函数。节约性能,节省计算次数。vue的计算属性即使用类似的规则。

js-设计模式学习和理解,第4张
let mk = {
    cache: {},
    getNum (n) {
        if (typeof this.cache[n] !== 'undefined') {
            debugger
            return this.cache[n]
        }
        let m = (n * 10000)/99 - 58
        this.cache[n] = m
        
        return m
    }
}
console.log(mk.getNum(5))
console.log(mk.getNum(18))
console.log(mk.getNum(5))

命名空间设计模式

主要为了防止命名变量冲突,所以把一类的变量放到一个对象里边去,调用的时候通过这个对象来调用。

js-设计模式学习和理解,第5张

策略模式

比如,下载文件,wifi形势下自动下载,4g情况下提示下载,3g情况不下载

js-设计模式学习和理解,第6张

let celue = {
    phone: /iPhone|iPad|Android/i.test(window.navigator.userAgent), // 是否移动端
    fast (n) {
        console.log(n, '这里是pc端,可以快速运行')
    },
    slow (n) {
        console.log(n, '这里是移动端,请注意性能消耗')
    },
    test (n) {
        if (this.phone) {
            this.slow(n)
        } else {
            this.fast(n)
        }
    }
}
celue.test(5)

模板模式

流程都是一样的,只有一小部分不一样,用参数传入即可

js-设计模式学习和理解,第7张
// 这里用奶茶店用来举例,首先写上模板方法,即父方法
function Milk () {
    // this.init()
}
Milk.prototype = {
    init () {
        this.addWater()
        this.cailiao()
        this.make()
        this.over()
    },
    cailiao () {
        // 加材料, 这个方法要在子类重写,所以这个父方法要抛错,防止直接调用
        throw '请您在子类重写此方法'
    },
    addWater () {
        // 加水
        console.log(`加入了${this.water}L开水`)
    },
    make () {
        // 搅拌
        console.log('搅拌了25分钟')
    },
    over () {
        console.log(`已经生产好了一杯香喷喷的${this.type}奶茶`)
    }
}

// 【子类,利用父级模板】
function Apple (type, water) {
    // 生产苹果奶茶
    this.type = type
    this.water = water
}
Apple.prototype = new Milk()
Apple.prototype.cailiao = function () {
    console.log('珍珠被开水浸泡成软软的了')
}

let my = new Apple('珍珠', 15)
my.init() // 加入了15L开水  珍珠被开水浸泡成软软的了   搅拌了25分钟   已经生产好??一杯香喷喷的珍珠奶茶

// 【子类,利用父级模板】
function Paer (type, water) {
    // 生产苹果奶???
    this.type = type
    this.water = water
}
Paer.prototype = new Milk()
Paer.prototype.cailiao = function () {
    console.log('红豆被开水浸泡成软软的了')
}

let my1 = new Paer('红豆', 23)
my1.init() //  加入了23L开水   红豆被开水浸泡成软软的了    搅拌了25分钟    已经生产好了一杯香喷喷的红豆奶茶

命令模式

为了降低对象之间的耦合性,降低之后方便新增功能,也方便实现不同的功能
所以, 命令模式的核心就在于分离。专人做专事,遇到专人的事,你只需要命令他去做就可以了,不需要个关心中间环节。

js-设计模式学习和理解,第8张
// 这里用去饭店点菜做菜的逻辑说明
/*
    逻辑: 
    点餐人员, 关注对象: 菜单
    厨房老大, 关注对象: 分配给谁做
    厨师,关注:  什么菜
    服务员, 关注: 用户
*/

//  【厨师】
let cook1 = {
    name: '王守义',
    cook (type) {
        let str = this.name + '炒了一盘 '
        switch (type) {
            case '土豆':{
                str += '土豆片,加了好多辣椒'
                break
            }
            case '西红柿':{
                str += '西红柿鸡蛋,加了好多白糖'
                break
            }
            default: {
                str = this.name + '不会做' + type
                break
            }
        }
        console.log(str)
    }
}

let cook2 = {
    name: '老干妈',
    cook (type) {
        let str = this.name + '做了一锅 '
        switch (type) {
            case '土豆':{
                str += '土豆块,加了好多醋'
                break
            }
            case '西红柿':{
                str += '西红柿牛腩汤,加了好多盐'
                break
            }
            default: {
                str = this.name + '不会做' + type
                break
            }
        }
        console.log(str)
    }
}

let waiter = {
    cooker: [],
    addCook (obj) {
        this.cooker.push(obj)
    },
    start (list, call) {
        console.log('服务员接到订单开始分配订单给厨师:')
        list.forEach((item,index) => {
            if (index % 2== 0) {
                this.cooker[0].cook(item)
            } else {
                this.cooker[1].cook(item)
            }
        })
        call()
    }
}
waiter.addCook(cook1)
waiter.addCook(cook2)

let user1 = {
    list: ['土豆', '西红柿'],
    order () {
        console.log('用户1 点完了菜')
        waiter.start(this.list, () => {
            console.log('用户1吃到了:', JSON.stringify(this.list))
        })
    }
}

let user2 = {
    list: ['西红柿', '豆角'],
    order () {
        console.log('??户2 点完了菜')
        waiter.start(this.list, () => {
            console.log('用户2吃到了:', JSON.stringify(this.list))
        })
    }
}
user1.order()
user2.order()

map 和 set

map 和 set 其实都是es5现有数据类型的拓展:
set是array的拓展,区别是set 的元素不能重复,如果是复杂类型的set 则需要保障内存地址不重复
map 则是对象类型的拓展。不同的是对象类型的key必须是字符串,而map的可以可以是任何值

set 方法

set 还有一种拓展叫做WeakSet, 跟set的区别是:
1.WeakSet 的成员必须是对象,如果添加的不是对象则会报错
2.WeakSet 的成员是弱引用,假如某个对象只有在WeakSet中的引用,则垃圾回收机制会回收该对象
3.WeakSet 因为值可能被回收,所以没有size属性,也不能forEach遍历

let tset = new Set([1,2,6]) // Set(3) {1, 2, 6}
tset.add(5) // Set(3) {1, 2, 5, 6}
tset.add(6) // Set(3) {1, 2, 5, 6} // 6重复了,所以添加失败
tset.add('6') // Set(3) {1, 2, 5, 6, '6'}   set 里用的是===相等,所以6 和 '6'可以同时存在
tset.delete[6] // 返回true,   最新值 Set(3) {1, 2, 5, '6'}
tset.has(7) //  false, 检查是否有7
tset.size // 4  set的长度   size 是属性
tset.forEach(item => console.log(item) // 1, 2, 5, '6'  set支持forEach遍历
tset.clear() // Set(0) {} 清空set

let tset1 = new WeakSet([{a:1},{b:5}]) // WeakSet {{…}, {…}}
// WeakSet 【有三个方法】
let tar4 = [1]
tset1.add(tar4)
tset1.delete(tar4) // 因为是引用,所以只能使用变量,无法删除字面量表示的对象
tset1.has(tar4) // true

map 方法

map 也有一个对应的WeakMap结构,与map区别是:
1.WeakMap 只接受对象作为key,
    2.WeakMap 键名的对象是弱引用,可以被垃圾回收机制回收
let ta = {a:1}
let m = new Map()
// 因为是引用类型的,key必须是变量,才能保证是指向相应对象的内存地址
m.set(ta, {b:5}) // Map(1) {{…} => {…}}  set(key,value)
m.get(ta) // {b: 5}
m.has(ta) // true
m.size // 1,   map长度
m.forEach(item => console.log(item)) // map 支持forEach 方法
m.delete(ta) // true ,返回true或false,表示删除成功或失败
m.has(ta) // false
m.clear() //  true  清空map方法

需要注意的是map如果要直接初始化数据,需要传入数组的list,形式如下

[['a', 5], ['b',7]]
声场的map值如下:
Map(2) {'a' => 5, 'b' => 7}
Map转数组,也是会转成

[['a', 5], ['b',7]]
形式,可以使用Array.from方法

let tm = new Map([['a', 5], ['b',7]])
console.log(tm) // Map(2) {'a' => 5, 'b' => 7}
let tar = Array.from(tm)
console.log(tar) // [['a', 5], ['b',7]]

迭代器模式

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
其实也就是不适用对象原有迭代的方式,自己封装的迭代方式

迭代器有以下角色:

迭代器模式主要包含以下角色:

抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。

具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。

抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。

具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

代码实现:

// 迭代器模式,不暴漏原始迭代方法,使用封装的方法来代理迭代对象
    class Iteration {
      constructor (object) {
        this.object = object
        this.current = 0
        this.all = object.length || Object.keys(object).length
        this.keys = Object.keys(object)
      }
      next () {
        if (this.current === this.all) { return null }
        else {
          let value = this.object[this.keys[this.current]]
          this.current++
          return value
        }
      }
    }
    let tobj = {a:5,b:6,c:7}
    let tobjIter = new Iteration(tobj)
    let value = tobjIter.next()
    while(value) {
      console.log(value)
      value = tobjIter.next()
    }

中介者模式

如果在一个系统中对象之间的联系呈现为网状结构,如上左图所示。对象之间存在大量的多对多联系,将导致系统非常复杂,这些对象既会影响别的对象,也会被别的对象所影响,这些对象称为同事对象,它们之间通过彼此的相互作用实现系统的行为。在网状结构中,几乎每个对象都需要与其他对象发生相互作用,而这种相互作用表现为一个对象与另外一个对象的直接耦合,这将导致一个过度耦合的系统。

而 “迪米特法则”告诉我们,可以引入一个“第三者”来降低现有系统中类之间的耦合度。

中介者模式可以使对象之间的关系数量急剧减少,通过引入中介者对象,可以将系统的网状结构变成以中介者为中心的星形结构 ,从上右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。

定义

如果在一个系统中对象之间存在多对多的相互关系,我们可以将对象之间的一些交互行为的细节从各个对象中分离出来,并集中封装在一个中介者对象中,并由该中介者进行统一协调,这样对象之间多对多的复杂关系就转化为相对简单的一对多关系。通过引入中介者来简化对象之间的复杂交互,中介者模式是“迪米特法则”的一个典型应用。
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。

中介者模式包含以下主要角色:

抽象中介者接口(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法(通信)。
具体中介者(ConcreteMediator)角色:实现中介者接口, 是抽象中介者的子类 ,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系, 维持了对各个同事对象的引用 ,因此它依赖于同事角色。
抽象同事类(Colleague)角色: 它定义各个同事类公有的方法,并声明了一些抽象方法来供子类实现,同时它维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。
具体同事类(Concrete Colleague)角色: 它是抽象同事类的子类;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中声明的抽象方法。

代码实现:

// 中介者模式,对象比较多的时候,想要访问其他对象会非常麻烦,这时候可以使用中介者来访问对象

    class Middle{
      constructor() {}
      addObject (object, name) {
        this[name] = object
      }
    }
    let mid = new Middle()
    let ta = {
      a: 10,
      b: 5,
      del () {
        delete this.a
      },
      add (value) {
        this.b += value
      }
    }

    let tb = {
      a: 20,
      b:15,
      read() {
        return this.a + this.b
      }
    }
    mid.addObject(ta, 'ta')
    mid.addObject(tb, 'tb')

    mid.ta.add.call(tb, 15)
    console.log(tb.b)

享元模式

面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象个数。当对象数量太多时,将导致运行带价过高,带来性能下降等问题。享元模式正式为解决这一类问题而诞生的。
享元模式(Flyweight Pattern)又称轻量级模式,是对象池的一种实现。类似于线程池,线程池可以不停的创建和销毁多个对象,消耗性能。提供了减少对象数量从而改善应用所需的对象结构的方式。其宗旨是共享细颗粒度对象,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,从此来降低内存的消耗,属于结构性模式。
享元模式把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
享元模式的本质是缓存共享对象,降低内存消耗。
享元模式有三个参与角色:
抽象享元角色(IFlyweight):享元对象抽象基类或者接口,同时定义出对象的外部状态和内部状态的接口或者实现;

具体享元角色(ConcreteFlyweight):实现抽象角色定义的业务。该角色的内部状态处理应该与环境无关,不能出现会有一个操作改变内部状态 ,同时修改了外部状态

享元模式的应用场景

当系统中多处需要同一组信息时,可以吧这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多处需要使用的地方,避免大量同一对象多次创建,消耗大量内存空间。
享元模式其实就是工厂模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法生成对象的,只不过享元模式中为工厂方法增加了缓存这一功能。主要总结为以下应用场景:

1、常常应用于系统底层的开发,以便解决系统的性能问题。
2、系统有大量相似的对象、需要缓存池的场景。

在生活中的享元模式也很常见,比如中介机构的房源共享,再比如全国社保联网。

代码实现:

// 享元模式, 如果一些信息在多处都需要调用,可以把它封装成一个对象,在使用的地方引用,避免多次创建重复信息的对象
    // 这里假设是个餐厅,食材信息是固定的,每道菜的分量成本就可以通过使用了多少mer1,和多少mer2,来计算
    let mer1 = {
      weight: 5,
      money: 10
    }
    let mer2 = {
      weight: 8,
      money:7
    }

    class Food1  {
      constructor(mer1, mer2) {
        this.mer1 = mer1
        this.mer2 = mer2
      }
      makeFood () {
         
      }
      get price () {
        return this.mer1.money * 2 + this.mer2.money
      }
    }

    class Food2  {
      constructor(mer1, mer2) {
        this.mer1 = mer1
        this.mer2 = mer2
      }
      makeFood () {
         
      }
      get price () {
        return this.mer1.money + this.mer2.money * 3
      }
    }

    let food1 = new Food1(mer1, mer2)
    let food2 = new Food2(mer1, mer2)
    console.log(food1.price, food2.price)

https://www.xamrdz.com/backend/3ds1935032.html

相关文章: