web前端进阶之js设计模式篇——上

设计模式,从设计到模式

  • 设计:设计原则(统一指导思想)
  • 模式:通过概念总结出的一些模板,可以效仿的固定式的东西(根据指导思想结合开发经验,总结出固定的样式或模板)

按类型分

创建型(对象的创建及生成)
image.png
组合型(对象和类是怎样的组合形式,一个类不一定能满足需求,通过组合的形式完成)
image.png
行为型(涵盖了开发中的一些常用的行为,如何设计才能满足需求)
image.png

image.png

工厂模式(实例化对象模式)

image.png

image.png
  • demo
//Creator是个工厂,有个create函数,工厂通过create创建Product
    class Product {
        constructor(name) {
            this.name = name
        }
        init() {
            alert(`${this.name}`)
        }
        fn1() {
            alert('this is fn1')
        }
        fn2() {
            alert('this is fn2')
        }
    }

    class Creator {
        create(name) {
            // 生成类的一个实例
            return new Product(name)
        }
    }
    // 测试
    // 生成一个工厂
    let creator = new Creator()
    // 通过工厂省城product的实例
    let p = creator.create('p')
    p.init()
    p.fn1()
    let p1 = creator.create('p1')
    p1.init()


    // 通过工厂模式将函数封装好,暴露一个接口即可
class jQuery {
    constructor(selector) {
        // 获取数组的slice
        let slice = Array.prototype.slice
        // 获取节点,利用slice.call将其结果返回给一个数组,因为可能是多个dom节点
        let dom = slice.call(document.querySelectorAll(selector))
        // 获取dom的长度
        let len = dom ? dom.length : 0
        // 进行循环
        for (let i = 0; i < len; i++) {
            // 将dom的数组元素赋值给this也就是实例的元素,元素的k就是数组的k,0,1,2...
            this[i] = dom[i]
        }
        // 赋值数组的长度
        this.length = len
        this.selector = selector || ''
    }
    append(node) {
        //...
    }
    addClass(name) {
        //...
    }
    html(data) {
        //...
    }
    // 此处省略若干 API
}

// 这个函数相当于工厂,封装了返回实例的操作
// 入口,这个$是个函数,函数里面返回一个jquery实例
window.$ = function(selector) {
   
    return new jQuery(selector)
}

console.log($(p))

题外话:读源码lib的意义
1、学习如何实现功能
2、学习设计思路
3、强制模拟好的代码

  • 设计原则验证
    1、构造函数和创建者分离
    2、符合开放封闭原则

单例模式

介绍
  • 系统中被唯一使用
  • 一个类只有一个实例(在内部使用实例,不可以在外部使用)
举例
  • 登录框
  • 购物车
说明
  • js中没有private这个私有属性关键字,typescript除外
java代码实例
image.png

image.png
js代码实例
// js中使用单例模式
    class SingleObject {
        // 在这里面定义的方法非静态,初始化实例时,都会有login()这个方法
        login() {
            console.log('login...')
        }

    }
    // 定义一个静态的方法,将方法挂载到class上面,无论SingleObject被new多少个,getInstance的方法只有一个
    SingleObject.getInstance = (function() {
        let instance
        return function() {
            // 如果没有则赋值,初始化
            if (!instance) {
                instance = new SingleObject();
            }
            // 有的话直接返回
            return instance
        }
    })()

    // js中的单例模式只能靠文档去约束,不能使用private关键字去约束
    // 测试:注意这里只能使用静态函数getInstance,不能使用new SingleObject()
    let obj1 = SingleObject.getInstance()
    obj1.login()
    let obj2 = SingleObject.getInstance()
    obj2.login()

    // 单例模式(唯一的),每次获取的都是一个东西,所以他两三等,否则就不是单例模式
    console.log(obj1 === obj2) //true
  • jquery中的$是单例模式
// 不管用几个jquery文件,他自字内部会处理,引用同一个$
    // 单例模式的思想,如果有直接用,没有的话,实例化一个
    if(window.jQuery != null){
        return window.jQuery
    }else{
        // init
    }
  • 模拟登录框
 class LoginForm {
        constructor() {
            // 初始化属性,默认是隐藏的
            this.state = "hide"
        }
        show() {
            // 当前是show,alert已经显示
            if (this.state == "show") {
                alert('已经显示')
                // return 不在执行了
                return
            }
            // 如果不等于show,就继续执行,让他等于show
            this.state = "show"
            alert('登录框显示成功')
        }
        hide() {
            if (this.state = "hide") {
                alert('已经隐藏')
                return
            }
            this.state = "hide"
            alert('登录框隐藏')
        }
    }
    // 自定义函数在这里定义的目的,就是为了加一个闭包的变量instace,防止变量污染
    LoginForm.getInstance = (function() {
        let instace
        // 这里函数目的在于获取单例的变量
        return function() {
            if (!instace) {
                instace = new LoginForm()
            }
            return instace
        }

    })()

    // 测试

    // 模拟第一个页面的登录框让其显示
    let login1 = LoginForm.getInstance()
    login1.show()

    // 模拟第二个页面的登录框让其隐藏
    let login2 = LoginForm.getInstance()
    login2.show()
  • vue中的store也是单例模式
  • 设计原则验证
    1、符合单一职责原则,只实例化唯一的对象
    2、没法具体体现开放封闭原则,但是绝不违反开放封闭原则

适配器模式

介绍
  • 旧接口格式和使用者不兼容
  • 中间加一个适配转换接口
js代码演示
    // 旧接口
        class Adaptee{
            specificRequest(){
                return '这是旧接口'
            }
        }
        // 新接口
        class Target{
            constructor(){
                this.adaptee = new Adaptee()
            }
            request(){
                // 进行转换
                let info = this.adaptee.specificRequest()
                return `${info}--新接口`

            }
        }
        // 测试
        let target = new Target()
        console.log(target.request())
  • 封装旧接口(旧接口和新接口起冲突时,使用适配器修改)


    image.png

    image.png
  • vue中的computed就是适配模式
  • 设计原则验证
    1、将旧接口和使用者进行分离(所有分离和解耦的方式都属于开放封闭原则)
    2、符合开放封闭原则

装饰器模式(既能使用原有的功能,又能使用装饰后的功能)

介绍
  • 为对象添加新功能
  • 不改变其原有的结构和功能
举例
  • 比如手机可以打电话、发短信,我们在原有的基础上装个保护壳,防止摔落时损坏
js代码演示
    class Circle {
        draw() {
            console.log('我要画一个圆')
        }
    }

    class Decorator {
        constructor(circle) {
            this.circle = circle
        }
        draw() {
            this.circle.draw()
            this.setBorder(circle)
        }
        setBorder(circle) {
            console.log('我还画了条线')
        }
    }
    // 测试

    // 想引用某个类的时候将他实例化,以参数的形式进行传递进去
    let circle = new Circle()
    circle.draw()
    console.log('+++++')
    let dec = new Decorator(circle)
    dec.draw()
ES7装饰器

考虑到浏览器兼容问题,需要安装插件支持
1、npm install babel-plugin-transform-decorators-legacy --save-dev
2、对babel进行配置


image.png
  • 装饰类
// 1、首先定义一个类
// 2、定义一个函数,传个target参数
// 3、在Demo这个类上面,加个@testDemo(这个就是装饰器),通过@语法将这个类装饰一遍,target这个参数其实就是Demo这个类

@testDemo
class Demo{

}

function testDemo(target){
    target.isDec = true
}

alert(Demo.isDec)
// +++++++++++++++++++++++++++++++
// 装饰器原理

/*@decorator
class A {}*/

// 等同于

class A {}

// 将A定义成decorator函数执行一遍的返回值(相当于A在decorator执行了一遍),没有的话返回A

A = decorator(A) || A

// +++++++++++++++++++++++++++++++

// 可以加个参数
@testDemo1(false)
class Demo1{

}


function testDemo1(isDec){
    // 这里面返回一个函数,装饰器返回的都是一个函数
    return function(target){
        target.isDec = isDec
    }
}
alert(Demo1.isDec)
  • 装饰类—mixinshi示例

// 先定义一个mixins,获取解构的集合...list
function mixins(...list) {
    // return一个函数,装饰器都是一个函数
  return function (target) {
    // 将target.prototype和...list集合 混合在一起
    Object.assign(target.prototype, ...list)
  }
}
// 混合的对象
const Foo = {
  foo() { alert('foo') }
}

// 加个mixins的装饰器,将Foo对象传递进去
// @mixins(Foo) 一执行,最后返回的是个函数,通过@关键字使用装饰器,将Foo里面的属性和并到target.prototype上
@mixins(Foo)
class MyClass {}

 // MyClass()里面没有Foo,通过装饰器将他们合并便具有了foo()方法
let obj = new MyClass();
obj.foo() // 'foo'
  • 装饰方法—示例1
// 所有装饰器都是一个函数,最后将其返回
/*
@param target——Person这个类
@param name——当前属性点
@param descriptor——属性描述
*/
function readonly(target, name, descriptor){
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  // 将可写的关闭
  descriptor.writable = false;
  // 将其返回,所有被@readonly装饰过的只可读不可写
  return descriptor;
}

class Person {
    constructor() {
        this.first = 'A'
        this.last = 'B'
    }

    @readonly
    // name()是Person里面的一个方法,我们想要只能获取不能修改,所以加个@readonly装饰器
    name() { return `${this.first} ${this.last}` }
}

var p = new Person()
console.log(p.name())
p.name = function () {} // 这里会报错,因为 name 加了装饰器,就是只读属性

  • 装饰方法—示例2
function log(target, name, descriptor) {
    
  // 获取value,其实就是add函数
  var oldValue = descriptor.value;
// 将value重新赋值一个函数
  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    // 将原本的函数执行一下也就是add(),apply改变this的指向
    return oldValue.apply(this, arguments);
  };

  return descriptor;
}
// 定义一个Math类,希望在执行add执行的时候打印一个日志
class Math {
  // 被装饰过的add()就是一个行的value,新的value就是先打印日志,再去做之前加法的执行,最后将其返回
  @log
  add(a, b) {
    return a + b;
  }
}

const math = new Math();
// 执行 add 时,会自动打印日志,因为有@log装饰器
const result = math.add(2, 4);
console.log('result', result);
  • 第三方库core-decorators(提供常用的装饰器)
    1、安装npm i core-decorators --save
    2、直接引用,就不需要自己写了
import {readonly} from "core-decorators"

 class Person {
    @readonly
    name(){
        return 'this is test'
    }
 }
let p = new Person()

// 可读不可写
 alert(p.name())  

// 此装饰器提醒用户方法已废弃
import { deprecate } from 'core-decorators';

class Person {
  @deprecate('这个里面可以自己添加提示语')
  facepalm() {}

  @deprecate('We stopped facepalming')
  facepalmHard() {}

  @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming

person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
//     See http://knowyourmeme.com/memes/facepalm for more details.
  • 设计原则验证
    1、将现有对象和装饰器进行分离,两者独立存在(解耦)
    2、符合开放封闭原则

代理模式

介绍
  • 使用者无权访问目标对象
  • 中间加代理,通过代理做授权和控制
js代码演示
class ReadImg{
    constructor(filename){
        this.filename = filename
        this.loadImg()
    }
    display(){
        console.log('display---'+ this.filename)
    }
    loadImg(){
        console.log('loading---'+this.filename)
    }

}


class ProxyImg{
    constructor(filename){
        this.readImg = new ReadImg(filename)
    }
    display(){
        this.readImg.display()
    }
}

let proxyImg = new ProxyImg('1.png')

proxyImg.display()
使用场景

1、网页事件代理

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style type="text/css">
        *{
            padding: 0;
            margin: 0;
        }
        ul li{
            list-style: none;
        }
    </style>
</head>
<body>
<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
</body>
<script type="text/javascript">
    let ul = document.getElementById('ul')
    ul.addEventListener('click',function(e){
        if(e.target.nodeName === "LI"){
            alert(e.target.innerHTML)
        }
    })
</script>
</html>

2、this的指向

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style type="text/css">
        *{
            padding: 0;
            margin: 0;
        }
        ul li{
            list-style: none;
        }
    </style>
</head>
<body>
<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
</body>
<script type="text/javascript">
    let ul = document.getElementById('ul')
    ul.addEventListener('click',function(){
        var _this = this;
    // 箭头函数里面this的执行是window,所以利用代理的方式将其值赋给_this
        setTimeout(function(){
            _this.style.backgroundColor = 'red'
        },2000)
    })
</script>
</html>

3、$.proxy


image.png

4、ES6 Proxy

// 明星
let star = {
    name: '张XX',
    age: 25,
    phone: '13910733521'
}

// 经纪人agent 
// star 代理的对象,监听代理的获取 get 和设置 set 属性
// 注意代理的接口要和原生的一样 比如要知道name,就写name
let agent = new Proxy(star, {
    // target代理对象, key就是代理对象的值
    get: function (target, key) {
        if (key === 'phone') {
            // 返回经纪人自己的手机号
            return '18611112222'
        }

        if (key === 'price') {
            // 明星不报价,经纪人报价
            return 120000
        }
        // 如果不是在这个两种情况,直接返回target[key]
        return target[key]
    },
    
    set: function (target, key, val) {
        // 这是我们自己定义的价格
        if (key === 'customPrice') {
            if (val < 100000) {
                // 最低 10w,小于10万,报错
                throw new Error('价格太低')
            } else {
                target[key] = val
                // 这里写renturn true 要不然不会赋值成功
                return true
            }
        }
    }
})

// 测试+++++++++++
// 主办方
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)

// 想自己提供报价(砍价,或者高价争抢)
agent.customPrice = 150000
// agent.customPrice = 90000  // 报错:价格太低
console.log('customPrice', agent.customPrice)
  • 设计原则验证
    1、代理类和目标类分离,隔离开目标类和使用者
    2、符合开放封闭原则

代理模式、适配模式、装饰器模式三者的区别

  • 适配器:提供一个不同的接口(进行格式的转换)
  • 代理模式:提供一模一样的接口(无权使用主类,所以进行代理,提供一个一模一样的接口)
  • 装饰器模式:扩展功能,原有功能不变且可直接使用
  • 代理模式:显示原有功能,但是经过限制或阉割之后的

外观模式

介绍
  • 为子系统中的一组接口提供了一个高层接口
  • 使用者使用了这个高层接口


    image.png
场景

传多个参数都可适用


image.png
设计原则验证

1、不符合单一职责和开放封闭原则,因此谨慎使用,不可滥用(不要为了设计而设计,而是为了使用而设计)

观察者模式★

介绍
  • 发布 & 订阅(定好东西,付了款,会有人上门送,比如订牛奶、报纸啊等)
  • 一对多(N)(可以同时订购牛奶,报纸,两者之间没什么冲突)
js代码
// 主题,保存状态,接收状态变化,状态变化后触发所有观察者对象
class Subject {
    constructor() {
        // 状态
        this.state = 0
        // 所有观察者为一个数组
        this.observers = []
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
        this.notifyAllObservers()
    }
    // 添加一个新的观察者
    attach(observer) {
        this.observers.push(observer)
    }
    // 循环所有的观察者
    notifyAllObservers() {
        this.observers.forEach(observer => {
            // 遍历的每个元素执行update方法
            observer.update()
        })
    }
}

// 观察者,等待被触发
class Observer {
    constructor(name, subject) {
        this.name = name
        this.subject = subject
        // 将自己添加进去,把观察者添加到主题当中
        this.subject.attach(this)
    }
    update() {
        console.log(`${this.name} update, state: ${this.subject.getState()}`)
    }
}

// 测试代码
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
let o3 = new Observer('o3', s)

s.setState(1)
s.setState(2)
s.setState(3)


场景

1、网页事件绑定
所有的事件监听都是观察者模式,所有事件写好(订阅)之后,等待被执行


image.png

2、Promise中的then
在then里面写好逻辑之后,不会马上触发,等待Promise的状态发生改变时触发


image.png

image.png

3、jquery中的callbacks
这是个底层的api,比如使用ajax时,就会到这个api里面执行


image.png

4、node.js自定义事件
  • event
// 基础api的引用
const EventEmitter = require('events').EventEmitter

const emitter1 = new EventEmitter()
emitter1.on('some', info => {
    // 监听 some 事件
    console.log('f1',info)
})
emitter1.on('some', info => {
    // 监听 some 事件
    console.log('f2',info)
})
// 触发 some 事件
emitter1.emit('some','XXX')



// +++++++++++++++++++++++++++++++++++
const emitter = new EventEmitter()
emitter.on('sbowName', name => {
    console.log('event occured ', name)
})
// emit 时候可以传递参数过去
emitter.emit('sbowName', 'zhangsan')  


// +++++++++++++++++++++++++++++++++++
// 继承
// 任何构造函数都可以继承 EventEmitter 的方法 on emit
class Dog extends EventEmitter {
    constructor(name) {
        super()
        this.name = name
    }
}
var simon = new Dog('simon')
// 因为 simon 继承了 Dog ,所以便有他的 on 方法
simon.on('bark', function () {
    console.log(this.name, ' barked')
})
setInterval(() => {
    // 每一秒触发bark,都回去执行  console.log(this.name, ' barked')
    simon.emit('bark')
}, 500)

  • stream
// stream 用到自定义事件
const fs = require('fs')
// 读取文件的 Stream
const readStream = fs.createReadStream('./data/file1.txt')  

// 此方法是看文件有多少字符
let length = 0
// 监听这个流
// 将数据处理的函数和结束的函数定义好,直接去执行流,等待触发即可
readStream.on('data', function (chunk) {
    // chunk读一点,吐出一点
    length += chunk.toString().length
})
readStream.on('end', function () {
    console.log(length)
})


  • readline
var fs = require('fs')
var readline = require('readline');

// 查看有多少行
var rl = readline.createInterface({
    input: fs.createReadStream('./data/file1.txt')
});

var lineNum = 0
// 这里是监听一行一行的数据
rl.on('line', function(line){
    lineNum++
});
rl.on('close', function() {
    console.log('lineNum', lineNum)
});
  • 处理http请求
var http = require('http')

function serverCallback(req, res) {
    var method = req.method.toLowerCase() // 获取请求的方法
    if (method === 'get') {
    }
    if (method === 'post') {
        // 接收 post 请求的内容
        var data = ''
        req.on('data', function (chunk) {
            // “一点一点”接收内容
            console.log('chunk', chunk.toString())
            data += chunk.toString()
        })
        req.on('end', function () {
            // 接收完毕,将内容输出
            console.log('end')
            res.writeHead(200, {'Content-type': 'text/html'})
            res.write(data)
            res.end()
        })
    }
    
}
http.createServer(serverCallback).listen(8081)  // 注意端口别和其他 server 的冲突
console.log('监听 8081 端口……')
  • vue和react组件生命周期触发
    组件其实都是构造函数,生成一个组件相当于构造函数初始化实例
  • vue watch


    image.png
设计原则验证
  • 主题和观察者分离,不是主动触发而是被动监听,两者解耦
  • 符合开放封闭原则

迭代器模式

介绍

1、顺序访问一个集合(有序列表,数组,对象是个无序列表)
2、使用者无需知道集合的内部结构(封装,目的在于生成一个访问机制,不需要外界知道内部结构)

示例
  • 三个不同的数据结构遍历的方法有三种


    image.png
  • 统一的遍历方式


    image.png
代码演示
class Iterator {
    constructor(conatiner) {
        this.list = conatiner.list
        this.index = 0
    }
    next() {

        if (this.hasNext()) {
             // 如果还有下一项,直接返回当前这一项的index++
            return this.list[this.index++]
        }
        // 如果没有,则返回null
        return null
    }
    hasNext() {
        // 判断有没有下一项 
        // this.index >= this.list.length 这句话的意思是有没有到头
        if (this.index >= this.list.length) {
            return false
        }
        // 如果没有就是还有下一项
        return true
    }
}

class Container {
    constructor(list) {
        this.list = list
    }
    // 生成遍历器
    getIterator() {
        // 遍历器是有依据的,所以要传递一个参数
        return new Iterator(this)
    }
}

// 测试代码
let container = new Container([1, 2, 3, 4, 5])
// 生成一个遍历器,通过这个遍历器可以兼容所有的有序结集合的数据结构
let iterator = container.getIterator()
while(iterator.hasNext()) {
    console.log(iterator.next())
}

ES6 Iterator

  • ES6 Iterator为何存在


    image.png
  • ES6 Iterator是什么


    image.png
  • 示例

// 传入的data可以是任意的
function each(data) {
    // 生成遍历器,类似jquery生成的遍历器
    let iterator = data[Symbol.iterator]()

    // 有数据时返回 {value: 1, done: false}
    // console.log(iterator.next())  
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next())
    // 没有值的时候返回undefined,done 等于 true 就结束了
    // console.log(iterator.next())  // 没有数据时返回 {value: undefined, done: true}
    // 因为不知道所遍历数据的长度length,所以使用while循环
    let item = { done: false }
    while (!item.done) {
        // 每次获取next
        item = iterator.next()
        // 判断done是否结束
        if (!item.done) {
            console.log(item.value)
        }
    }
}


// 测试
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
// 如果是对象,可以利用Map.set
let m = new Map()
m.set('a', 100)
m.set('b', 200)

each(arr)
each(nodeList)
each(m)
image.png
设计原则验证
  • 迭代器对象和目标对象分离
  • 迭代器将使用者与目标对象隔离开
  • 符合开放封闭原则

状态模式

介绍

1、一个对象有状态变化
2、每次状态变化都会触发一个逻辑
3、不能总是用if...else来控制

js代码
// 把状态抽象出来
// 状态(红绿灯)
class State {
    constructor(color) {
        this.color = color
    }
    handle(context) {
        console.log(`turn to ${this.color} light`)
        // 设置状态
        context.setState(this)
    }
}
// 主体 实例
class Context {
    constructor() {
        this.state = null
    }
    // 获取状态
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
    }
    
}

// 测试代码
let context = new Context()

let greed = new State('greed')
let yellow = new State('yellow')
let red = new State('red')

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

场景

1、有限状态机

  • 有限个状态、以及在这些状态之间的变化
  • 第三方库,npm i javascript-state-machine --save
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <p>有限状态机</p>
    <button id="btn"></button>
    
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="./03-javascript-state-machine.js"></script>
    <script>
        // 状态机模型
        var fsm = new StateMachine({
            init: '收藏',  // 初始状态,待收藏
            transitions: [
                {
                    name: 'doStore', 
                    from: '收藏',
                    to: '取消收藏'
                },
                {
                    name: 'deleteStore',
                    from: '取消收藏',
                    to: '收藏'
                }
            ],
            methods: {
                // 监听执行收藏
                onDoStore: function () {
                    alert('收藏成功') // 可以post请求
                    updateText()
                },
                // 取消收藏
                onDeleteStore: function () {
                    alert('已取消收藏')
                    updateText()
                }
            }
        })

        var $btn = $('#btn')

        // 点击事件
        $btn.click(function () {
            if (fsm.is('收藏')) {
                fsm.doStore(1)
            } else {
                fsm.deleteStore()
            }
        })

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

        // 初始化文案
        updateText()
    </script>
</body>
</html>

2、实现简单的Promise

  • promise是个class
  • 在初始化的时候传进去一个函数,函数有2个参数resolve、reject
  • 要实现then的方法,then接收2个参数成功和失败


    image.png
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="./03-javascript-state-machine.js"></script>
    <script>
    // 状态机模型
    var fsm = new StateMachine({
        // 初始化状态
        init: 'pending',
        // 变化
        transitions: [{
                // 事件名称
                name: 'resolve',
                from: 'pending',
                to: 'fullfilled'
            },
            {
                // 事件名称
                name: 'reject',
                from: 'pending',
                to: 'rejected'
            }
        ],
        methods: {
            // 监听resolve成功
            onResolve: function(state, data) {
                // 参数:state - 当前状态机实例; data - fsm.resolve(xxx) 执行时传递过来的参数
                data.successList.forEach(fn => fn())
            },
            // 监听reject失败
            onReject: function(state, data) {
                // 参数:state - 当前状态机实例; data - fsm.reject(xxx) 执行时传递过来的参数
                data.failList.forEach(fn => fn())
            }
        }
    })

    // 定义 Promise
    class MyPromise {
        constructor(fn) {
            // 存储起来
            this.successList = []
            this.failList = []

            fn(() => {
                // resolve 函数 我们定义的name
                fsm.resolve(this)
            }, () => {
                // reject 函数
                fsm.reject(this)
            })
        }
        // 实现then函数
        then(successFn, failFn) {
            this.successList.push(successFn)
            this.failList.push(failFn)
        }
    }

    // 测试代码
    function loadImg(src) {
        // 初始化传递2个参数
        const promise = new MyPromise(function(resolve, reject) {
            var img = document.createElement('img')
            img.onload = function() {
                resolve(img)
            }
            img.onerror = function() {
                reject()
            }
            img.src = src
        })
        return promise
    }
    var src = 'http://www.imooc.com/static/img/index/logo_new.png'
    // result就是返回promise的实例
    var result = loadImg(src)
    console.log(result)
    // 此处没有链式操作
    result.then(function(img) {
        console.log('success 1')
    }, function() {
        console.log('failed 1')
    })
    result.then(function(img) {
        console.log('success 2')
    }, function() {
        console.log('failed 2')
    })
    </script>
</body>

</html>
设计原则验证
  • 将状态对象和主题对象分离,状态的变化逻辑单独处理
  • 符合开放封闭原则

设计模式文档

http://www.runoob.com/design-pattern/singleton-pattern.html

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

推荐阅读更多精彩内容