Published on

JavaScript设计模式(下篇)

Authors
  • avatar
    Name
    Hansuku
    Twitter

迭代器模式

迭代器模式意在,js 中遍历方法众多,foreach、for、for...of 等等针对的是不一样的数据格式,而迭代器模式的思想就是聚合这些遍历方法,我不需要知道我现在的数据是个什么样的格式,只要知道是要遍历的就可以了。 JavaScript 共有四种数据集合:数组 Array、对象 Object、Map、Set。后两个是 ES6 添加的

/*
假设遍历以下dom
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p> */
var arr = [1,2,3,4,5]
var nodeList = document.getElementsByTagName('a')
var $p = $p('p')
//遍历数组
arr.forEach(function(item){
    console.log(item)
})
//遍历nodeList
var i,length = nodeList.length
for(i = 0;i < length;i++){
    console.log(nodeList[i])
}
//遍历$a
$a.each(function(key,elem){
    console.log(key,elem)
})

像上面这种代码,不同的数据格式调用不同的遍历方法,下面来整合在一起

/*
假设遍历以下dom
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p> */
var arr = [1,2,3,4,5]
var nodeList = document.getElementsByTagName('a')
var $p = $p('p')

function each(data){
    var $data = $(data)  //生成迭代器
    $data.each(function(key,val){
        console.log(key,val)
    })
    each(arr)
    each(nodeList)
    each($p)
}

class Container{
    constructor(list){
        this.list = list
    }
    getIterator(){
        return new Iterator(this)
    }
}
class Iterator{
    constructor(container){
        this.list = container.list
        this.index = 0
    }
    nex(){
        if(this.hasNext()){
            return this.list[this.index++]
        }
        return null
    }
    hasNext(){
        if(this.index >= this.list.length){
            return false
        }
        return true
    }
}

let arr = [1,2,3,4,5,6]
let container = new Container(arr)
let iterator = container.getIterator()
while(iterator,hasNext()){
    console.log(iterator.next())
}

是否支持iterator的数据集合可以通过Symbol.interator查询

function each(data){
    let iterator = data[Symbol.iterator]()
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())
}

let arr = [1,2,3,4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a',100)
m.set('b',100)

each(arr)
// each(nodeList)
// each(m)

结合上面的代码 当我们不知道 data 是什么数据集合时 我们通过 while 来循环

function each(data){
    let iterator = data[Symbol.iterator]()
    let item = {done: false}
    while(!item.done){
        item = iterator.next()
        if(!item.done){
            console.log(item.value)
        }
    }
}

let arr = [1,2,3,4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a',100)
m.set('b',100)

each(arr)
each(nodeList)
each(m)

ES6 中的for...of就是一个iterator

function each(data){
    //带有遍历器特性的对象 : data[Symbol.iterator] 有值
    for(let item of data){
        console.log(data)
    }
}

let arr = [1,2,3,4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a',100)
m.set('b',100)

each(arr)
each(nodeList)
each(m)

状态模式

状态模式意在分离主题对象和状态对象 减少使用if...elseswitch...case 下面将模拟红绿灯切换的场景

class State{
    constructor(color){
        this.color = color
    }
    handle(context){
        console.log(`trun to ${this.color} ligth`)
        context.setState(this)
    }
}

class Context{
    constructor(){
        this.state = null
    }
    getState(){
        return this.state
    }
    setState(state){
        this.state = state
    }
}

let context = new Context()

let green = new State('greem')
let yellow = new State('yellow')
let red = new State('red')

//绿灯亮了
green.handle(context)
console.log(context.getState())
//黄灯亮了
yellow.handle(context)
console.log(context.getState())
//红灯亮了
red.handle(context)
console.log(context.getState())

状态模式中有一个场景是有限状态机,JavaScript-state-machine 是一个有限状态机的第三方库

有限状态机实现收藏和取消收藏

// npm install --save javascript-state-machine
import StateMachine from 'javascript-state-machine'
//初始化状态机模型
let fsm = new StateMachine({
    init:'收藏',
    transitions:[
        {
            name: 'doStore',
            from: '收藏',
            to: '取消收藏'
        },
        {
            name: 'deleteStore',
            from: '取消收藏',
            to: '收藏'
        }
    ],
    methods:{
        // 监听执行收藏
        onDoStore:function(){
            alert('收藏成功')
            updateText()
        },
        // 监听取消收藏
        onDeleteStore:function(){
            alert('取消收藏成功')
            updateText()
        }
    }
})

//<button id="btn1"></button>
let btn = document.getElementById('btn1')
console.log(btn)
//按钮点击事件
btn.addEventListener('click',function(){
    if(fsm.is('收藏')){
        fsm.doStore()
    }else{
        fsm.deleteStore()
    }
},false)

//更新按钮文案
function updateText(){
    btn.innerText = fsm.state
}

//初始化文案
updateText()

Promise 有限状态机

  • Promise 三种状态:pending fullfilled rejected
  • pending -> fullfilled 或者 pending -> rejected
  • 不可逆向变化

其他非典型 JavaScript 设计模式

行为型
策略模式
模板方法模式
职责链模式
命令模式
备忘录模式
中介者模式
访问者模式
解释器模式

原型模式

clone 自己,生成一个新对象 Js 中的应用-Object.create

//一个原型对象
let prototype = {
    getName:function(){
        return this.first + ' ' + this.last
    },
    say:function(){
        alert('hello')
    }
}
//创建原型x
let x = Object.create(prototype)
x.first = 'A'
x.last = 'B'
console.log(x.getName())
x.say()
//创建原型y
let y = Object.create(prototype)
y.first = 'C'
y.last= 'D'
console.log(y.getName())
y.say()

prototype 是 ES6 Class 的一种底层原理 class 是面向对象的基础,并不服务与某个模式

桥接模式

把抽象化与实现化解耦,使得二者完全独立

class Color{
    constructor(name){
        this.name = name
    }
}
class Shape{
    constructor(name ,color){
        this.name = name
        this.color = color
    }
    draw(){
        console.log(`${this.color.name} ${this.name}`)
    }
}

let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle',red)
circle.draw() //red circle
let triangle = new Shape('triangle', yellow)
triangle.draw() //yellow triangle

组合模式

生成树形结构,表示“整体-部分”关系 整体和单个节点的操作是一致的 整体和单个节点的数据结构也保持一致

// dom
// <div id="div1" class="container">
//    <p>123</p>
//    <p>456</p>
// </div>
//vnode
{
    tag: 'div',
    attr: {
        id: 'div1',
        className: 'container',VC    },
    children: [
        {
            tag: 'p',
            attr:{},
            children:['123']
        },
        {
            tag:'p',
            attr:{},
            children:['456']
        }
    ]
}

享元模式

共享内存(主要考虑内存,而非效率)

// 如果都绑定到a标签 对内存开销太大
// <div id="div1">
//     <a href="#">a1</a>
//     <a href="#">a2</a>
//     <a href="#">a3</a>
//     <a href="#">a4</a>
//     <!-- 无限下拉列表 -- >
// </div>
var div1 = document.getElementById('div1')
div1.addEventListener('click',function(e){
    var target = e.target
    if(e.nodeName === 'A'){
        alert(EventTarget.innerHtml)
    }
})

策略模式

不同策略分开处理 避免出现大量if...else或者switch...case js 中无经典场景

源代码:

class User{
    constructor(type){
        this.type = type
    }
    buy(){
        if(this.type === 'ordinary'){
            console.log('普通用户购买')
        }else if(this.type === 'member'){
            console.log('会员购买')
        }else if(this.type === 'vip'){
            console.log('VIP用户购买')
        }
    }
}

var u1 = new User('ordinary')
u1.buy()
var u2 = new User('member')
u2.buy()
var u3 = new User('vip')
u3.buy()

策略模式:

class OrdianryUser{
    buy(){
        console.log('普通用户购买')
    }
}
class MemberUser{
    buy(){
        console.log('普通用户购买')
    }
}
class VipUser{
    buy(){
        console.log('vip用户购买')
    }
}
var u1 = new OrdianryUser()
u1.buy()
var u2 = new MemberUser()
u2.buy()
var u3 = new VipUser()
u3.buy()

模板方法模式

class Action{
	handle(){
		handle1()
		handle2()
		handle3()
	}
	handle1(){
		console.log('1')
	}
	handle2(){
		console.log('2')
	}
	handle3(){
		console.log('3')
	}
}

职责链模式

一步操作可能分为多个职责角色来完成 把这些角色都分开,然后用一个链串起来 将发起者和各个角色的处理者隔离

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('组长')
let a2 = new Action('经理')
let a3 = new Action('总监')
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

命令模式

执行命令时,发布者和执行者分开

class Receiver{
    exec(){
        console.log('执行')
    }
}
class Command{
    constructor(receiver){
        this.receiver = receiver
    }
    cmd(){
        console.log('触发命令')
        this.receiver.exec()
    }
}
class Invoker{
    constructor(command){
        this.command = command
    }
    invoke(){
        console.log('开始')
        this.command.cmd()
    }
}

//士兵
let soldier = new Receiver()
//小号手
let trumpeter = new Command(soldier)
//将军
let general = new Invoker(trumpeter)
general.invoke()

JS 中的应用场景

网页富文本编辑器操作,浏览器封装了一个命令对象 document.execCommand('bold') document.execCommand('undo')

备忘录模式

随时记录一个对象的状态变化 随时可恢复之前的某个状态(撤销) 未找到 JS 中经典应用,除了一些工具(如编辑器)

//状态备忘
class Memento{
    constructor(content){
        this.content = content
    }
    getContent(){
        return this.content
    }
}
//备忘列表
class CareTaker{
    constructor(){
        this.list = []
    }
    add(memento){
        this.list.push(memento)
    }
    get(index){
        return this.list[index]
    }
}
//编辑器
class Editor{
    constructor(){
        this.content = null
    }
    setContent(content){
        this.content = content
    }
    getContent(){
        return this.content
    }
    saveContentToMemento(){
        return new Memento(this.cotent)
    }
    getContentFromMemento(memento){
        this.content = memento.getContent()
    }
}
//test
let edit = new Editor()
let careTaker = new CareTaker()
eidtor.setContent('111')
eidtor.setContent('222')
careTaker.add(editor.saveContentToMemento())//存储备忘录
editor.setContent('333')
careTaker.add(editor.saveContentToMemento())//存储备忘录
editor.setContent('444')
console.log(editor.getContent())
editor.getContentFromMemento(careTaker.get(1))//撤销
console.log(editor.getContent())
editor.getContentFromMemento(careTaker.get(0))//撤销
console.log(editor.getContent())

中介者模式

//买房子例子
//中介者
class Mediator{
    constructor(a,b){
        this.a = a
        this.b = b
    }
    setA(){
        let number = this.b.number
        this.a.setNumber(number * 100)
    }
    setB(){
        let number = this.a.number
        this.b.setNumber(number / 100)
    }
}
class A{
    constructor(){
        this.number = 0
    }
    setNumber(num,m){
        this.number = num
        if(m){
            m.setB()
        }
    }
}
class B{
    constructor(){
        this.number = 0
    }
    setNumber(num,m){
        this.number = num
        if(m){
            m.setA()
        }
    }
}
//test
let a = new A()
let b = new B()
let m = new Mediator(a,b)
a.setNumber(100,m)
console.log(a.number,b.number)
b.setNumber(100,m)
console.log(a.number,b.number)

访问者模式

将数据操作和数据结构进行分离 使用场景不多

解释器模式

描述语言语法如何定义,如何解释和编译 用于专业场景

例子-jQuery 模拟购物车

用到的模式:工厂模式 单例模式 装饰器模式 观察者模式 状态模式 模板方法模式 代理模式

UML 类图

源码地址

https://github.com/Hansuku/javascript-design-pattern

总结

  • 工厂模式:$('xxx')创建商品
  • 单例模式:购物车
  • 装饰器模式:打点统计
  • 观察者模式:网页事件,Promise
  • 状态模式:添加到购物车 & 从购物车删除
  • 模板方法模式:渲染有统一的方法,内部包含了各模块渲染
  • 代理模式:打折商品信息处理
*/}