为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
JavaScript

设计模式,从设计到模式

  • 设计:设计原则(统一指导思想)

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

按类型分

创建型(对象的创建及生成)

webp

image.png

组合型(对象和类是怎样的组合形式,一个类不一定能满足需求,通过组合的形式完成)

webp

image.png

行为型(涵盖了开发中的一些常用的行为,如何设计才能满足需求)

webp

image.png


webp

image.png

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

webp

image.png


webp

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代码实例

webp

image.png


webp

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())
  • 封装旧接口(旧接口和新接口起冲突时,使用适配器修改)


    webp

    image.png


    webp

    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进行配置


webp

image.png

  • 装饰类

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

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

alert(Demo.isDec)// +++++++++++++++++++++++++++++++// 装饰器原理/*@decoratorclass A {}*/// 等同于class A {}// 将A定义成decorator函数执行一遍的返回值(相当于A在decorator执行了一遍),没有的话返回AA = decorator(A) || A// +++++++++++++++++++++++++++++++// 可以加个参数@testDemo1(false)class Demo1{

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

// 先定义一个mixins,获取解构的集合...listfunction 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 facepalmingperson.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


webp

image.png


4、ES6 Proxy

// 明星let star = {    name: '张XX',    age: 25,    phone: '13910733521'}// 经纪人agent // star 代理的对象,监听代理的获取 get 和设置 set 属性// 注意代理的接口要和原生的一样 比如要知道name,就写namelet 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、符合开放封闭原则

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

  • 适配器:提供一个不同的接口(进行格式的转换)

  • 代理模式:提供一模一样的接口(无权使用主类,所以进行代理,提供一个一模一样的接口)

  • 装饰器模式:扩展功能,原有功能不变且可直接使用

  • 代理模式:显示原有功能,但是经过限制或阉割之后的

外观模式

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

  • 使用者使用了这个高层接口


    webp

    image.png

场景

传多个参数都可适用


webp

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、网页事件绑定
所有的事件监听都是观察者模式,所有事件写好(订阅)之后,等待被执行


webp

image.png

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


webp

image.png


webp

image.png


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


webp

image.png


4、node.js自定义事件

  • event

// 基础api的引用const EventEmitter = require('events').EventEmitterconst 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 emitclass 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')// 读取文件的 Streamconst 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


    webp

    image.png

设计原则验证
  • 主题和观察者分离,不是主动触发而是被动监听,两者解耦

  • 符合开放封闭原则

迭代器模式

介绍

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

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


    webp

    image.png

  • 统一的遍历方式


    webp

    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为何存在


    webp

    image.png

  • ES6 Iterator是什么


    webp

    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.setlet m = new Map()
m.set('a', 100)
m.set('b', 200)

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

webp

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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./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>



作者:jia林
链接:https://www.jianshu.com/p/4b110e4c3bcd


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消