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() // 跑
工厂模式
> 应用: 比如需要一个复杂的对象,但是使用的时候有不需要关心创建过程,就可以先创建一个工厂,用来生产复杂对象。
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果汁
观察者模式
观察者模式通常都是后端用,前端用的不多。
/*
构成
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的计算属性即使用类似的规则。
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))
命名空间设计模式
主要为了防止命名变量冲突,所以把一类的变量放到一个对象里边去,调用的时候通过这个对象来调用。
策略模式
比如,下载文件,wifi形势下自动下载,4g情况下提示下载,3g情况不下载
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)
模板模式
流程都是一样的,只有一小部分不一样,用参数传入即可
// 这里用奶茶店用来举例,首先写上模板方法,即父方法
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分钟 已经生产好了一杯香喷喷的红豆奶茶
命令模式
为了降低对象之间的耦合性,降低之后方便新增功能,也方便实现不同的功能
所以, 命令模式的核心就在于分离。专人做专事,遇到专人的事,你只需要命令他去做就可以了,不需要个关心中间环节。
// 这里用去饭店点菜做菜的逻辑说明
/*
逻辑:
点餐人员, 关注对象: 菜单
厨房老大, 关注对象: 分配给谁做
厨师,关注: 什么菜
服务员, 关注: 用户
*/
// 【厨师】
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)