JS 设计模式

JS 设计模式笔记。

面向对象

搭建开发环境

npm / webapck / webpack-dev-server / babel
babel-core babel-loader babel-polyfill babel-preset-env

什么是面向对象

概念

类(对象的模板)、对象(实例)

class People {
constructor(name, age) {
this.name = name
this.age = age
}
eat() {
console.log(`${this.name} eat something`)
}
speak() {
console.log(`My name is ${this.name}, age ${this.age}`)
}
}

let zhang = new People('zhang', 20)
zhang.eat()
zhang.speak()

let wang = new People('wang', 21)
wang.eat()
wang.speak()

三要素:继承封装多态

  • 继承:子类继承父类
    • 继承是父类,公共的,不仅仅服务于 Student
    • 继承可将公共方法抽离出来,提高复用,减少冗余
  • 封装:数据的权限和保密
    • public 完全开放
    • protected 对子类开放
    • private 对自己开放
    • ES6不支持,可以用 typescript 演示
    • 减少耦合,不该外露的不外露
    • 利于数据、接口权限的管理
    • ES6 目前不支持,一般认为_开头的属性是私有的
  • 多态:同一接口不同实现
    • JS 应用少
      • 保持子类的开放性和灵活性
      • 面向接口编程
// 继承
class People {
constructor(name, age) {
this.name = name
this.age = age
}
eat() {
console.log(`${this.name} eat something`)
}
speak() {
console.log(`My name is ${this.name}, age ${this.age}`)
}
}

class Student extends People {
constructor(name, age, number) {
super(name, age)
this.number = number
}
study() {
console.log(`${this.name} study`)
}
}

let xiaoming = new Student('xiaoming', 10, 'A1')
xiaoming.study()
console.log(xiaoming.number)
let xiaohong = new Student('xiaohong', 11, 'A2')
xiaohong.study()


// 多态
class People {
constructor(name) {
this.name = name
}
saySomething() {

}
}
class A extends People {
constructor(name) {
super(name)
}
saySomething() {
console.log('I am A')
}
}
class B extends People {
constructor(name) {
super(name)
}
saySomething() {
console.log('I am B')
}
}
let a = new A('a')
a.saySomething()
let b = new B('b')
b.saySomething()

JS 应用举例

class jQuery {
constructor(selector) {
let slice = Array.prototype.slice
let dom = slice.call(document.querySelectorAll(selector))
let len = dom ? dom.length : 0
for (let i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
append(node) {

}
addClass(name) {

}
html(data) {

}
// 此处省略若干 API
}
window.$ = function(selector) {
return new jQuery(selector)
}

面向对象的意义

  • 程序执行:顺序、判断、循环——结构化
  • 面向对象:数据结构化
  • 对于计算机,结构化的才是最简单的
  • 编程应该 简单 & 抽象

UML类图

统一建模语言。

  • 类图:UML 包含很多种图,本次相关的是类图
  • 关系:主要讲解泛化和关联
  • 泛化表示继承,关联表示引用

类图:
image

关系:
image

设计原则

  • 什么是设计
    • 即按照哪一种思路或者标准来实现功能
    • 功能相同,可以有不同设计方案来实现
    • 伴随着需求增加,设计的作用才能体现出来
  • UNIX / LINUX 设计哲学
    • 准则1:小即是美
    • 准则2:让每个程序只做好一件事
    • 准则3:快速建立原型
    • 准则4:舍弃高效率而取可移植性
    • 准则5:采用纯文本来存储数据
    • 准则6:软件复用
    • 准则7:使用 shell 脚本来提高杠杆效应和可移植性
    • 准则8:避免强制性的用户界面
    • 准则9:让每个程序都称为过滤器
    • 小准则:允许用户定制环境
    • 尽量使操作系统内核小而轻量化
    • 使用小写并尽量简短
    • 沉默是金
  • SOLID五大设计原则
    • S 单一职责
      一个程序只做好一件事
      如果功能过于复杂就拆分开,每个部分保持独立
    • O 开放封闭
      对扩展开放,对修改封闭
      增加需求,扩展新代码,而非修改已有代码
      这是软件设计的终极目标
    • L 李氏置换
      子类能覆盖父类
      父类能出现的地方子类就能出现
      JS 中使用较少(弱类型&继承使用较少)
    • I 接口独立
      保持接口的单一独立,避免出现“胖接口”
      JS 中没有接口(typescript 例外),使用较少
      类似于单一职责原则,这里更关注接口
    • D 依赖倒置
      面向接口编程,依赖于抽象而不依赖于具体
      使用方只关注接口而不关注具体类实现
      JS 中使用较少(没有接口&弱类型)

设计模式

从设计到模式

  • 创建型
    • 工厂模式
    • 单例模式
    • 原型模式
  • 结构型
    • 适配器模式
    • 装饰器模式
    • 代理模式
    • 外观模式
    • 桥接模式
    • 组合模式
    • 享元模式
  • 行为型

    • 策略模式
    • 迭代器模式
    • 模板方法模式
    • 职责连模式
    • 观察者模式
    • 命令模式
    • 备忘录模式
    • 状态模式
    • 访问者模式
    • 中介者模式
    • 解释器模式
  • 明白每个设计的道理和用意

  • 通过经典应用体会它的真正使用场景
  • 自己编码时多思考,尽量模仿

两个面试题

01
image

class Car {
constructor(number, name) {
this.number = number
this.name = name
}
}
class Kuaiche extends Car {
constructor(number, name) {
super(number, name)
this.price = 1
}
}
class Zhuanche extends Car {
constructor(number, name) {
super(number, name)
this.price = 2
}
}

class Trip {
constructor(car) {
this.car = car
}
start() {
console.log(`行程开始,名称: ${this.car.name}, 车牌号: ${this.car.price}`)
}
end() {
console.log('行程结束,价格: ' + (this.car.price * 5))
}
}

let car = new Kuaiche(100, '桑塔纳')
let trip = new Trip(car)
trip.start()
trip.end()

02

image

// 车
class Car {
constructor(num) {
this.num = num
}
}

// 入口摄像头
class Camera {
shot(car) {
return {
num: car.num,
inTime: Date.now()
}
}
}

// 出口显示器
class Screen {
show(car, inTime) {
console.log('车牌号', car.num)
console.log('停车时间', Date.now() - inTime)
}
}

// 停车场
class Park {
constructor(floors) {
this.floors = floors || []
this.camera = new Camera()
this.screen = new Screen()
this.carList = {}
} in (car) {
// 获取摄像头的信息:号码 时间
const info = this.camera.shot(car)
// 停到某个车位
const i = parseInt(Math.random() * 100 % 100)
const place = this.floors[0].places[i]
place.in()
info.place = place
// 记录信息
this.carList[car.num] = info
}
out(car) {
// 获取信息
const info = this.carList[car.num]
const place = info.place
place.out()

// 显示时间
this.screen.show(car, info.inTime)

// 删除信息存储
delete this.carList[car.num]
}
emptyNum() {
return this.floors.map(floor => {
return `${floor.index} 层还有 ${floor.emptyPlaceNum()} 个车位`
}).join('\n')
}
}

// 层
class Floor {
constructor(index, places) {
this.index = index
this.places = places || []
}
emptyPlaceNum() {
let num = 0
this.places.forEach(p => {
if (p.empty) {
num = num + 1
}
})
return num
}
}

// 车位
class Place {
constructor() {
this.empty = true
} in () {
this.empty = false
}
out() {
this.empty = true
}
}

// 测试代码------------------------------
// 初始化停车场
const floors = []
for (let i = 0; i < 3; i++) {
const places = []
for (let j = 0; j < 100; j++) {
places[j] = new Place()
}
floors[i] = new Floor(i + 1, places)
}
const park = new Park(floors)

// 初始化车辆
const car1 = new Car('A1')
const car2 = new Car('A2')
const car3 = new Car('A3')

console.log('第一辆车进入')
console.log(park.emptyNum())
park.in(car1)
console.log('第二辆车进入')
console.log(park.emptyNum())
park.in(car2)
console.log('第一辆车离开')
park.out(car1)
console.log('第二辆车离开')
park.out(car2)

console.log('第三辆车进入')
console.log(park.emptyNum())
park.in(car3)
console.log('第三辆车离开')
park.out(car3)

工厂模式

  • 介绍
  • UML && 代码演示
  • 使用场景

介绍:

  • 将 new 操作单独封装
  • 遇到 new 时,就要考虑是否该用工厂模式
class Product {
constructor(name) {
this.name = name
}
init() {

}
fun1() {

}
}
class Creator {
create(name) {
return new Product(name)
}
}
// test
let creator = new Creator()
let p1 = creator.create('p1')
p1.init()

场景:

  • jQuery:$(‘div’)
    • $(‘div’) 和 new $(‘div’) 有何区别(查看上述 jQuery 例子)
  • React.createElement
    image
  • vue 异步组件
    image

学习经典库的方法:

  • 学习功能如何实现
  • 学习实现思路?怎么设计的
  • 强制自己写代码,模拟(刻意练习)
  • 模仿练习(拿来主义)

设计原则验证:

  • 构造函数和创建者分离
  • 符合开放封闭原则
  • 单例模式

    介绍:
  • 系统中被唯一使用的
  • 一个类只有一个实例。

登陆框、购物车…

说明:

  • 单例模式需要用到 java 的特性(private)
  • ES6 中没有(typescript 除外)
  • 只能用 java 代码来演示 UML 图的内容

场景:

  • jquery 只有一个 $
  • 模拟登陆框
  • 购物车
  • vuex 和 redux 中的 store

jQuery

// jQuery 只有一个 $
if (window.jQuery != null) {
return window.jQuery
} else {
// init
}
class SingleObject {
login() {
console.log('login...')
}
}
SingleObject.getInstance = (function() {
let instance
return function() {
if (!instance) {
instance = new SingleObject();
}
return instance
}
})()

// 测试
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log(obj1 === obj2)
class LoginForm {
constructor() {
this.state = 'hide'
}
show() {
if (this.state === 'show') {
console.log('已经显示')
return
}
this.state = 'show'
console.log('登录框已显示')
}
hide() {
if (this.state === 'hide') {
console.log('已经隐藏')
return
}
this.state = 'hide'
console.log('登录框已隐藏')
}
}
LoginForm.getInstance = (function() {
let instance
return function() {
if (!instance) {
instance = new LoginForm();
}
return instance
}
})()

// 一个页面中调用登录框
let login1 = LoginForm.getInstance()
login1.show()
// login1.hide()

// 另一个页面中调用登录框
let login2 = LoginForm.getInstance()
login2.show()

// 两者是否相等
console.log('login1 === login2', login1 === login2)

设计原则验证:

  • 符合单一职责原则,只实例化唯一的对象
  • 没法具体开放封闭原则,但是绝对不违反开放封闭原则

适配器模式

插头,转接口…

介绍:

  • 旧接口格式和使用者不兼容
  • 中间加一个适配转换接口

UML类图:

image

演示:

class Adaptee {
specificRequest() {
return '德国标准插头'
}
}

class Target {
constructor() {
this.adaptee = new Adaptee()
}
request() {
let info = this.adaptee.specificRequest()
return `${info} - 转换器 - 中国标准插头`
}
}

// 测试代码
let target = new Target()
let res = target.request()
console.log(res)

场景:

  • 封装旧接口
  • vue computed
    image
// 当前ajax封装
ajax({
url: 'getData',
type: 'post',
dataType: 'json',
data: {
id: "123"
}
}).done(function(){})
// 历史代码
// $.ajax({...})

// 做一层适配器
var $ = {
ajax: function(options) {
return ajax(options)
}
}

设计原则验证:

  • 将旧接口和使用者进行分离
  • 符合开放封闭原则

装饰器模式

介绍:

  • 为对象添加新功能
  • 不改变其原有的结构和功能

例子:手机壳

UML类图:

image

image

ES7 装饰器:

  • babel插件:babel-plugin-transform-decorators-legacy
  • 库:core-decorators(常用装饰器已经写好了,直接拿来用)
// 装饰类
// @testable
// class MyTestableClass {
// // ...
// }

// function testable(target) {
// target.isTestable = true;
// }

// console.log(MyTestableClass.isTestable) // true


function mixins(...list) {
return function (target) {
Object.assign(target.prototype, ...list)
}
}

const Foo = {
foo() { console.log('foo') }
}

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // 'foo'

// 装饰方法
function readonly(target, name, descriptor) {
// descriptor对象原来的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}

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

@readonly
name() {
return `${this.first} ${this.last}` }
}

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


// another
function log(target, name, descriptor) {
var oldValue = descriptor.value;

descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};

return descriptor;
}

class Math {
@log
add(a, b) {
return a + b;
}
}

const math = new Math();
const result = math.add(2, 4);
console.log('result', result);
// core-decorators


// import { readonly } from 'core-decorators'

// class Person {
// @readonly
// name() {
// return 'zhang'
// }
// }

// let p = new Person()
// console.log(p.name())
// // p.name = function () { /*...*/ } // 此处会报错


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.

设计原则验证:

  • 将现有对象和装饰器进行分离,两者独立存在
  • 符合开放封闭原则

代理模式

介绍:

  • 使用者无权访问目标对象
  • 中间加代理,通过代理做授权和控制

例子:

  • 科学上网
  • 明星经纪人

UML类图:

image

演示:

image

场景:

  • 网页事件代理
    image
  • jQuery $.proxy
    image
    image
  • ES6 Proxy
    image
// 明星
let star = {
name: '张XX',
age: 25,
phone: '13910733521'
}

// 经纪人
let agent = new Proxy(star, {
get: function(target, key) {
if (key === 'phone') {
// 返回经纪人自己的手机号
return '18611112222'
}
if (key === 'price') {
// 明星不报价,经纪人报价
return 120000
}
return target[key]
},
set: function(target, key, val) {
if (key === 'customPrice') {
if (val < 100000) {
// 最低 10w
throw new Error('价格太低')
} else {
target[key] = val
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)

访问代理,接口地址是不会变的。
设计原则验证:

  • 代理类和目标类分离,隔离开目标类和使用者
  • 符合开放封闭原则

代理模式 VS 适配器模式

  • 适配器模式:提供一个不同的接口
  • 代理模式:提供一模一样的接口

代理模式 VS 装饰器模式

  • 装饰器模式:扩展功能,原有功能不变且可直接使用
  • 代理模式:显示原有功能,但是经过限制或者阉割之后的

    外观模式

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

image

观察者模式

介绍:

  • 发布 & 订阅
  • 一对多

UML类图:

image

例子:

  • 点咖啡,点好后坐等被叫
// 主题,接收状态变化,触发每个观察者
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 => {
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)

场景:

  • 网页事件绑定
    image
  • Promise
    image
    image
  • jQuery callbacks
    image
  • nodeJs 自定义事件
// jquery callbacks
var callbacks = $.Callbacks() // 注意大小写
callbacks.add(function(info) {
console.log('fn1', info)
})
callbacks.add(function(info) {
console.log('fn2', info)
})
callbacks.add(function(info) {
console.log('fn3', info)
})
callbacks.fire('gogogo') // 发布
callbacks.fire('fire')
// nodejs 自定义事件
const EventEmitter = require('events').EventEmitter

const emitter1 = new EventEmitter()
emitter1.on('some', () => {
// 监听 some 事件
console.log('some event is occured 1')
})
emitter1.on('some', () => {
// 监听 some 事件
console.log('some event is occured 2')
})
// 触发 some 事件
emitter1.emit('some')


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

---

// 任何构造函数都可以继承 EventEmitter 的方法 on emit
class Dog extends EventEmitter {
constructor(name) {
super()
this.name = name
}
}
var simon = new Dog('simon')
simon.on('bark', function() {
console.log(this.name, ' barked')
})
setInterval(() => {
simon.emit('bark')
}, 500)

node Stream 用到了自定义事件:

// 文件太大,用流的形式读取
// 监听文件有多少字符
var fs = require('fs')
var readStream = fs.createReadStream('./data/file1.txt') // 读取文件的 Stream

var length = 0
readStream.on('data', function(chunk) {
length += chunk.toString().length
})
readStream.on('end', function() {
console.log(length)
})

// 监听一行一行的数据(监听文件有多少行)
var readline = require('readline');
var fs = require('fs')

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)
});

其他场景:

  • nodeJs 中: 处理 http 请求;多进程通讯
  • vue 和 react 组件生命周期触发
    image
  • vue watch
    image
// node 处理 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 端口……')

设计原则验证:

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

    迭代器模式

    介绍:
  • 顺序访问一个集合(有序集合)
  • 使用者无需知道集合的内部结构(封装)

UML类图:

image

使用 jQuery 示例:

var arr = [1, 2, 3]
var nodeList = document.getElementsByTagName('p')
var $p = $('p')

// 要对这三个变量进行遍历,需要写三个遍历方法
// 第一
arr.forEach(function(item) {
console.log(item)
})
// 第二
var i, length = nodeList.length
for (i = 0; i < length; i++) {
console.log(nodeList[i])
}
// 第三
$p.each(function(key, p) {
console.log(key, p)
})

// 如何能写出一个方法来遍历这三个对象呢
function each(data) {
var $data = $(data) // 生成迭代器
$data.each(function(key, p) {
console.log(key, p)
})
}
each(arr)
each(nodeList)
each($p)

class Iterator {
constructor(conatiner) {
this.list = conatiner.list
this.index = 0
}
next() {
if (this.hasNext()) {
return this.list[this.index++]
}
return null
}
hasNext() {
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())
}

使用场景:

  • JQuery each
  • ES6 Iterator

为何 ES6 Iterator 存在?

  • ES6 语法中,有序集合的数据类型已经很多
  • Array Map Set String TypedArray arguments NodeList
  • 需要有一个统一的遍历接口来遍历所有数据类型
  • 注意:object 不是有序集合,可以用 Map 代替

ES6 Iterator 是什么?

  • 以上数据类型,都有 [Symbol.iterator] 属性
  • 属性值是函数,执行函数返回一个迭代器
  • 这个迭代器就有 next 方法可顺序迭代子元素
  • 可运行 Array.prototype[Symbol.iterator] 来测试

ES6 Iterator 示例:

// 手动实现遍历器
function each(data) {
// 生成遍历器
let iterator = data[Symbol.iterator]()

// console.log(iterator.next()) // 有数据时返回 {value: 1, done: false}
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next()) // 没有数据时返回 {value: undefined, done: true}

let item = { done: false }
while (!item.done) {
item = iterator.next()
if (!item.done) {
console.log(item.value)
}
}
}

// 数组已经部署了 Iterator 接口
// for of 可以遍历 所有部署了 Iterator 接口的数据结构
function each(data) {
for (let item of data) {
console.log(item)
}
}

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

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

ES6 Iterator 与 Generator

  • Iterator 的价值不限于上述几个类型的遍历
  • 还有 Generator 函数的使用
  • 即只要返回的数据符合 Iterator 接口要求
  • 即可使用 Iterator 语法,这就是迭代器模式
// function* helloWorldGenerator() {
// yield 'hello';
// yield 'world';
// return 'ending';
// }

// var hw = helloWorldGenerator();
// console.log(hw.next())
// console.log(hw.next())
// console.log(hw.next())
// console.log(hw.next())

function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}

for (let v of foo()) {
console.log(v);
}

设计原则验证:

  • 迭代器对象和目标对象分离
  • 迭代器将使用者与目标对象分离开
  • 符合开放封闭原则

状态模式

介绍:

  • 一个对象有状态的变化
  • 每次状态变化都会触发一个逻辑
  • 不能总用 if…else 来控制

UML类图:

image

// 状态
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
}
setState(state) {
this.state = state
}
getState() {
return this.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())

场景:

  • 有限状态机
  • 写一个简单的 Promise
<script src="./03-javascript-state-machine.js"></script>
// 状态机模型
var fsm = new StateMachine({
init: '收藏', // 初始状态,待收藏
transitions: [{
name: 'doStore',
from: '收藏',
to: '取消收藏'
}, {
name: 'deleteStore',
from: '取消收藏',
to: '收藏'
}],
methods: {
// 监听执行收藏
onDoStore: function() {
console.log('收藏成功')
updateText()
},
// 监听取消收藏
onDeleteStore: function() {
console.log('已取消收藏')
updateText()
}
}
})

var $btn = $('#btn')

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

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

// 初始化文案
updateText()
// promise 简单实现
// 模型
var fsm = new StateMachine({
init: 'pending',
transitions: [{
name: 'resolve',
from: 'pending',
to: 'fullfilled'
}, {
name: 'reject',
from: 'pending',
to: 'rejected'
}],
methods: {
// 成功
onResolve: function(state, data) {
// 参数:state - 当前状态示例; data - fsm.resolve(xxx) 执行时传递过来的参数
data.successList.forEach(fn => fn())
},
// 失败
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 函数
fsm.resolve(this)
}, () => {
// reject 函数
fsm.reject(this)
})
}
then(successFn, failFn) {
this.successList.push(successFn)
this.failList.push(failFn)
}
}

// 测试代码
function loadImg(src) {
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'
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')
})

设计原则验证:

  • 将状态对象和主题对象分离,状态的变化逻辑单独处理
  • 符合开放封闭原则

其他设计模式

  • 不常用
  • 对应不到经典的应用场景

原型模式

  • clone 自己,生成一个新对象
  • java 默认有clone 接口,不用自己实现
    想一下:prototype
    应用:Object.create()

桥接模式

image
image

组合模式

  • 生成树形结构,表示“整体-部分”关系
  • 让整体和部分都具有一致的操作方式
  • 虚拟 DOM 中的 vnode 是这种形式,但数据类型简单

image

享元模式(共享元数据)

  • 共享内存
  • 相同数据,共享使用
  • 客户端不怎么考虑内存

例子:事件代理(ul>li)

策略模式

  • 不同策略分开处理
  • 避免出现大量 if…else 或者 switch…case

模板方法模式

class Action {
handle() {
this.handle1()
this.handle2()
this.handle3()
}
handle1() {

}
handle2() {

}
handle3() {

}
}

职责链模式

  • 一步操作可能分为多个职责角色来完成
  • 把这些角色都分开,然后用一个链串起来
  • 将发起者和各个处理者进行隔离
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()
}
}
}
// test
let a1 = new Action('组长')
let a2 = new Action('主管')
let a2 = new Action('经理')
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

命令模式

  • 执行命令时,发布者和执行者分开
  • 将军向小号手下达指令,小号手让士兵执行
  • JS应用
    • 网页富文本编辑器操作,浏览器封装了一个命令对象
    • document.execCommand(‘bold’)
    • document.execCommand(‘undo’)

image

备忘录模式

  • 随时记录一个对象的状态变化
  • 随时可以恢复之前的某个状态(如撤销)

编辑器案例:备忘列表存储编辑器中设置的每一项设置的内容

中介者模式

联想现实中介。
image
image

访问者模式&解释器模式

关于面试

重点设计模式要深入理解
非常用的,视业务场景需要选择

综合应用

介绍:

  • 用 jQuery 做一个模拟购物车的示例
  • 功能:显示购物列表、加入购物车、从购物车删除
  • 设计模式:工厂、单例、装饰器、观察者、状态、模板方法、代理

UML类图:
image

amenzai wechat
扫一扫上面二维码,获取更多内容。
欢迎各位老板打赏