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 应用少
- 保持子类的开放性和灵活性
- 面向接口编程
- 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 包含很多种图,本次相关的是类图
- 关系:主要讲解泛化和关联
- 泛化表示继承,关联表示引用
类图:
关系
设计原则
- 什么是设计
- 即按照哪一种思路或者标准来实现功能
- 功能相同,可以有不同设计方案来实现
- 伴随着需求增加,设计的作用才能体现出来
- UNIX / LINUX 设计哲学
- 准则1:小即是美
- 准则2:让每个程序只做好一件事
- 准则3:快速建立原型
- 准则4:舍弃高效率而取可移植性
- 准则5:采用纯文本来存储数据
- 准则6:软件复用
- 准则7:使用 shell 脚本来提高杠杆效应和可移植性
- 准则8:避免强制性的用户界面
- 准则9:让每个程序都称为过滤器
- 小准则:允许用户定制环境
- 尽量使操作系统内核小而轻量化
- 使用小写并尽量简短
- 沉默是金
- SOLID五大设计原则
- S 单一职责
一个程序只做好一件事
如果功能过于复杂就拆分开,每个部分保持独立 - O 开放封闭
对扩展开放,对修改封闭
增加需求,扩展新代码,而非修改已有代码
这是软件设计的终极目标 - L 李氏置换
子类能覆盖父类
父类能出现的地方子类就能出现
JS 中使用较少(弱类型&继承使用较少) - I 接口独立
保持接口的单一独立,避免出现“胖接口”
JS 中没有接口(typescript 例外),使用较少
类似于单一职责原则,这里更关注接口 - D 依赖倒置
面向接口编程,依赖于抽象而不依赖于具体
使用方只关注接口而不关注具体类实现
JS 中使用较少(没有接口&弱类型)
- S 单一职责
设计模式
从设计到模式
-
创建型
- 工厂模式
- 单例模式
- 原型模式
-
结构型
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
-
行为型
- 策略模式
- 迭代器模式
- 模板方法模式
- 职责连模式
- 观察者模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
-
明白每个设计的道理和用意
-
通过经典应用体会它的真正使用场景
-
自己编码时多思考,尽量模仿
两个面试题
01
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
// 车
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
- vue 异步组件
学习经典库的方法:
- 学习功能如何实现
- 学习实现思路?怎么设计的
- 强制自己写代码,模拟(刻意练习)
- 模仿练习(拿来主义)
设计原则验证:
- 构造函数和创建者分离
- 符合开放封闭原则
单例模式
介绍:
- 系统中被唯一使用的
- 一个类只有一个实例。
登陆框、购物车...
说明:
- 单例模式需要用到 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类图:
演示:
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
// 当前ajax封装
ajax({
url: 'getData',
type: 'post',
dataType: 'json',
data: {
id: "123"
}
}).done(function(){})
// 历史代码
// $.ajax({...})
// 做一层适配器
var $ = {
ajax: function(options) {
return ajax(options)
}
}
设计原则验证:
- 将旧接口和使用者进行分离
- 符合开放封闭原则
装饰器模式
介绍:
- 为对象添加新功能
- 不改变其原有的结构和功能
例子:手机壳
UML类图:
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类图:
演示:
场景:
- 网页事件代理
- jQuery $.proxy
- ES6 Proxy
// 明星
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 装饰器模式
- 装饰器模式:扩展功能,原有功能不变且可直接使用
- 代理模式:显示原有功能,但是经过限制或者阉割之后的
外观模式
介绍:
- 为子系统中的一组接口提供了一个高层接口
- 使用者使用这个高层接口
观察者模式
介绍:
- 发布 & 订阅
- 一对多
UML类图:
例子:
- 点咖啡,点好后坐等被叫
// 主题,接收状态变化,触发每个观察者
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)
场景:
- 网页事件绑定
- Promise
- jQuery callbacks
- 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 组件生命周期触发
- vue watch
// 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类图:
使用 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类图:
// 状态
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()
桥接模式
组合模式
- 生成树形结构,表示“整体-部分”关系
- 让整体和部分都具有一致的操作方式
- 虚拟 DOM 中的 vnode 是这种形式,但数据类型简单
享元模式(共享元数据)
- 共享内存
- 相同数据,共享使用
- 客户端不怎么考虑内存
例子:事件代理(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')
备忘录模式
- 随时记录一个对象的状态变化
- 随时可以恢复之前的某个状态(如撤销)
编辑器案例:备忘列表存储编辑器中设置的每一项设置的内容
中介者模式
联想现实中介。1
访问者模式&解释器模式
略
关于面试
重点设计模式要深入理解
非常用的,视业务场景需要选择
综合应用
介绍:
- 用 jQuery 做一个模拟购物车的示例
- 功能:显示购物列表、加入购物车、从购物车删除
- 设计模式:工厂、单例、装饰器、观察者、状态、模板方法、代理
UML类图: