javascript 设计模式

如何成为大师:刻意训练 越不练越不会

面向对象

为何使用面向对象

程序执行:顺序,判断,循环 -- 结构化(这三种就可以处理所有的程序)
面向对象:数据结构化
计算机:结构化的才是最简单的。
浏览器处理的就是字符串,对字符串的管理
编程:简单&抽象

三要素: 继承:子类继承父类

  • 可以将公共方法抽离出来

封装:数据的权限和保密

  • 定义属性(es6 不支持,typescript 支持)

    • public 完全开放(默认)
    • protected 对子类开放
    • private 对自己开放
  • 减少耦合,不该外漏的不外漏,一般以_开头的属性是 private 的

多态:同一接口不同实现

  • 两个子类定义同一个方法,执行的函数不同。即:两个子类定义两个同名的私有方法叫多态

例子:jq 就是一个类,

// 类
class Person {
	constructor(name, age) {
		this.name = name
		this.age = age
	}
	eat() {
		console.log(`${this.name} eating`)
	}
	speak() {
		console.log(`${this.name} speaking`)
	}
}
// 实例
const li = new Person('li', 20)
const yang = new Person('yang', 20)
li.eat()
li.speak()
yang.eat()
yang.speak()

// 继承
// 子类
class Student extends Person {
	constructor(name, age, number) {
		// super是将变量叫个父类处理,super给父类处理在子类中也可以通过this.name拿到值
		super(name, age)
		this.number = number
	}
	study() {
		console.log(`${this.name} study`)
	}
}

const xiaoming = new Student('小明', 10, '454949944')
xiaoming.study()
xiaoming.eat()

// jq
class JQ {
	constructor(selector) {
		let slice = Array.prototype.splice
		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.seletor = seletor || ''
	}
	append(node) {}
}
window.$ = function(selector) {
	return new JQ(selector)
}
const $p = $('p')

UML 类图

UML: unified modeling language 统一建模语言 类图:UML 有很多种图,本课程相关的是类图 关系:主要讲泛化(继承)和关联(引用)

属性
+: public
-:private
#:protected

设计原则

何为设计

按照哪一种思路或者标准来实现功能

《unix/linux 设计哲学》

什么是设计

  • 小即是美,每个程序只做好一件事
  • 快速建立原型
  • 放弃高效率而取可移植性
  • 采用纯文本来存储数据
  • 可复用
  • 使用 shell 脚本提高杠杆效应和可移植性
  • 让每个程序都称为过滤器

小准则

  • 允许用户定制环境
  • 各部分之和大于整体
  • 沉默是金
# 过滤所有json文件,再过滤带package的
ls | grep *.json | grep 'package'
# 如果什么都没有找到,那就什么都不输出

5 大设计原则

SOLID 原则

  • S: 单一职责--每个程序只做好一件事,各个部分之间相互利用,保持独立
  • o: 开放封闭--对扩展开放,对修改封闭。增加需求时,扩展新的代码,而不是修改已有的代码
  • l: 李氏置换--子类能够覆盖父类,父类能出现的地方子类就能出现(js 使用少)
  • i: 接口独立--保持接口单一独立,避免出现胖接口(js 使用少)
  • d: 依赖导致--面向接口编程,依赖于抽象而不依赖具体。使用方只关注接口而不关注类的实现(js 使用少)

promise 说明 SO

  • 单一职责:每个 then 中的逻辑只做好一件事
  • 开放封闭:如果有新的需求,扩展 then,而不是修改之前的
result
	.then(img => {
		// 每个 then 中的逻辑只做好一件事
		console.log(111)
		return img
	})
	.then(img => {
		// 如果有新的需求,扩展 then,而不是修改之前的
		console.log(222)
		return img
	})
	.catch(error => {
		console.log(error)
	})

面试题 1

  • 打车时可以打专车和快车,任何车都有车牌号和名称
  • 不同车价格不同,快车每公里 1 元,专车 2 元
  • 行程开始时显示车辆信息
  • 行程结束时显示打车金额(假定行程就 5 公里)
// car
class Car {
	constructor(number, name) {
		this.name = name
		this.number = number
	}
}
// 快车
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() {
		const { name, number } = this.car
		console.log(name, number, '==>行程开始')
	}
	end() {
		const { price } = this.car
		console.log(price * 5, '==>行程结束,总价')
	}
}

// 计算
const car = new Kuaiche(100, '桑塔纳')
const trip = new Trip(car)

trip.start()
trip.end()

面试题 2

  • 某停车场,分 3 层,每层 100 车位
  • 每个车位都能监控到车的驶入和离开
  • 车辆进入前,显示每层的空余车位数量
  • 车辆进入时,摄像头可识别车牌号和时间
  • 车辆出来时,出口显示器显示车牌号和停车时间
// 车辆
class Car {
	constructor(number) {
		this.number = number
	}
}

// 摄像头
class Camera {
	shot(car) {
		return {
			number: car.number,
			inTimes: Date.now(),
		}
	}
}

// 显示屏
class Screen {
	show(car, inTime) {
		console.log(car.number, 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.number]
		// 将停车位清空
		const place = info.place
		place.out()
		// 显示时间
		this.screen.show(car, info.inTime)
		// 清空记录
		delete this.carList[car.number]
	}
	getEmptyNum() {
		return this.floors.map(floor => {
			return `${floor.index}层还有${floor.getEmptyPlaceNum()}个车位`
		})
	}
}
// 层
class Floor {
	constructor(index, places) {
		this.index = index
		this.places = places || []
	}
	getEmptyPlaceNum() {
		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 car = new Car(112243093)

console.log(park.getEmptyNum())
park.in(car)
console.log(park.getEmptyNum())

设计模式

创建型:工厂模式,单例模式

结构型:适配器模式,装饰器模式,代理模式,外观模式

行为型:观察者模式,迭代器模式,状态模式

工厂模式(创建型)

将 new 操作单独封装 遇到 new 时就考虑是否要用工厂模式 构造函数和创建者分离 符合开放封闭原则

class Product {
	constructor(name) {
		this.name = name
	}
	init() {
		console.log('init')
	}
	fun1() {
		console.log('fun1')
	}
	fun1() {
		console.log('fun2')
	}
}
class Creator {
	create(name) {
		return new Product(name)
	}
}

// 实例
let creator = new Creator()
let p = creator.create('p1')
p.init()
p.fun1()

场景

  • $('div')
  • React.createElement
  • vue 异步组件

jquery $('div')就是工厂模式

  • 通过工厂把真正的构造函数和使用者隔开
  • $('div')new $('div')简洁
  • new $('div')书写麻烦,jq 的链式操作将成为噩梦
  • 一旦 jq 的名字变化,将是灾难性的
// 这个就是工厂模式,将示例return出去
window.$ = function(selector) {
	return new JQ(selector)
}
// React.createElement
class Vnode(tag, attrs, chilren) {

}
React.createElement = function(tag, attrs, children) {
    return new Vnode(tag, attrs, chilren)
}
// 应用
React.createElement('div', null,
    React.createElement('h3', null, '李月茹')
)
// vue
Vue.component('async-example', function(resolve, reject) {
    setTimeout(function() {
        resolve({
            templage: '<div>aaa</div>'
        })
    })
})

单例模式(创建型)

系统中被唯一使用的 一个类只有一个实例 只有内部才可以 new 实例,只能 new 一次(构造函数私有化)

class 的静态方法, 无论类被 new 多少个,静态方法只有一个

符合单一职责原则,只实例化一个对象

登录框 购物车

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) // true

// 不能直接用 new SingleObject()

场景 jq 只有一个$ 模拟登录框

// jq 只有一个`$`
if (window.jQuery != null) {
	return window.jQuery
} else {
	// 初始化
}

// 模拟登录框
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() // 显示成功
let login2 = LoginForm.getInstance()
login2.show() // 已经显示
let login3 = LoginForm.getInstance()
login3.hide() // 隐藏成功

适配器模式(结构型)

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

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

// 应用
let target = new Target()
target.request()

场景 旧接口的封装 vue computed

    // 自己封装的ajax
    ajax({
        url: '',
        type: ''
    }).done(function() {})
    但是因为历史原因,代码中全是$.ajax({})
    // 做一层适配
    const $ = {
        ajax: function(options) {
            return ajax(options)
        }
    }

装饰器模式(结构型)

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

手机壳

class Circle {
	draw() {
		console.log('画一个圆')
	}
}
class Decorator {
	constructor(circle) {
		this.circle = circle
	}
	draw() {
		this.circle.draw()
		this.setRedBorder(circle)
	}
	setRedBorder() {
		console.log('border')
	}
}

// 应用
let circle = new Circle()
circle.draw()

let dec = new Decorator(circle)
dec.draw()

场景 es7 装饰器 core-decorators 库

// es7 装饰器,装饰类
@decorator
class A {}
// 相当于
function decorator(target) {}
class A {}
A = decorator(A) || A
// 加参数
@testDec(true)
class Demo {}
function testDec(isDec) {
	return function(target) {
		target.isDec = isDec
	}
}
// 装饰方法
function readonly(target, name, descriptor) {
	// descriptor属性描述对象(Object.defineProperty中会用到)原来的值如下
	// {
	//     value: specifiedFunction,
	//     enumerable: false,
	//     configurable: true,
	//     writable: true
	// }
	descriptor.writable = false
	return descriptor
}
function log(target, name, descriptor) {
	const oldValue = descriptor.value
	descriptor.value = function() {
		console.log(111)
		return oldValue.apply(this, arguments)
	}
	return descriptor
}
class Person {
	constructor() {
		this.a = '李月茹'
	}
	@log
	@readonly
	name() {
		return this.a
	}
}
const p = new Person()
console.log(p.name())
p.name = function() {} // 报错,因为是只读的

代理模式(结构型)

使用者无权访问目标对象(有权限或者什么,无权使用,像有权一样) 中间加代理,通过代理做授权和控制 代理和目标对象要有一样的对外方法

class ReadImg {
	constructor(filename) {
		this.filename = filename
		htis.loadFromDisk()
	}

	display() {
		console.log('display..', this.filname)
	}

	loadFromDisk() {
		console.log('loading..', this.filname)
	}
}
class ProxyImg {
	constructor(filename) {
		this.realImg = new ReadImg(filename)
	}
	display() {
		this.realImg.display()
	}
}

// 应用
let proxyImg = new ProxyImg('1.png')
proxyImg.display()

场景 网页事件代理,(事件委托) jquery $.proxy es6 proxy

// 事件代理
// <div id="div1">
// 	<a>a1</a>
// 	<a>a1</a>
// 	<a>a3</a>
// 	<a>a4</a>
// </div>

document.getElementById('div1').addEventListener('click', e => {
	const target = e.target
	if (target.nodeName === 'A') {
		console.log(target.innerHTML)
	}
})
// jquery $.proxy
$('#div1').click(function() {
	// setTimeout里的this指的时window
	setTimeout(
		$.proxy(function() {
			$(this).css('background-color', 'red')
		}, this),
		1000
	)
})

// es6 proxy
// 明星
let star = {
    name: 'a',
    age: 10,
    phone: 2332232323
}
// 经纪人
let agent = new Proxy(start, {
    get: function(target, key) {
        if(key === 'phone') {
            // 经纪人自己的电话
            return 11111
        }
        if(key === 'price') {
            // 明星不报价,经纪人报价
            return 1200000
        }
        return target[key]
    }
    set: function(target, key, val) {
        if(key === 'customPrice') {
            if(val < 100000) {
                console.log('too low')
            } 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

外观模式(结构型)

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

不符合开放封闭原则,好用不要滥用

传参数,传不同的参数

function bindEvent(ele, type, selector, fn) {
	if ((fn = null)) {
		fn = selector
		selector = null
	}
}

// 调用
bindEvent(ele, 'click', '#div1', fn)
bindEvent(ele, 'click', fn)

观察者模式(行为型)

发布订阅 一对多

观察者:等待被触发 发布者:状态更改对所有观察者发布消息

// 主题,保存状态,状态变化之后触发所有观察者对象
class Subject {
	constructor() {
		this.state = 0
		this.observers = []
	}
	getState() {
		return this.state
	}
	setState(state) {
		this.state = state
		this.notifyAllObservers()
	}
	notifyAllObservers() {
		this.observers.forEach(observer => {
			observer.update()
		})
	}
	attach(observer) {
		this.observers.push(observer)
	}
}

// 观察者
class Observer {
	constructor(name, subject) {
		this.name = name
		this.subject = subject
		this.subject.attach(this)
	}
	update() {
		console.log(this.name, 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)

场景 网页事件绑定 promise (then) jq callbacks nodejs 自定义事件

迭代器模式(行为型)

顺序访问一个有序集合(array,map,set,string,TypedArray,Arguments,nodeList)object 不是,可以变成 map 使用者无需知道集合的内部结构(封装)

const arr = [1, 2, 3]
const nodeList = document.getElementsByTagName('a')
const $a = $('a')
// 如果逐个遍历要用不同的方法
arr.forEach(item => {
	console.log(item)
})

let i
let length = nodeList.length
for (i = 0; i < length; i++) {
	console.log(nodeList[i])
}

$a.each(function(key, ele) {
	console.log(key, ele)
})
// 用同一种方法完成上面3种的遍历
function each(data) {
	var $data = $(data) // 生成迭代器
	$data.each(function(key, val) {
		console.log(key, val)
	})
}

/*
	es6的写法
*/
class Iterator {
	constructor(Container) {
		this.list = Container.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 arr = [1, 2, 3]
let container = new Container(arr)
let iterator = container.getIterator()
while (iterator.hasNext()) {
	console.log(iterator.next())
}

场景 es6 Iterator array,map,set,string,TypedArray,Arguments,nodeList 都有一个[Symbol.iterator]属性 属性是一个函数,执行函数返回迭代器 迭代器有 next 方法可以顺序迭代子元素 返回的值中有 done 为 true 时遍历结束 可运行 Array.prototypeSymbol.iterator.next()来测试

// 封装迭代器
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)
		}
	}
}

// es6的语法 for...of 就是遍历迭代器的
function each(data) {
	for (let item of data) {
		console.log(item)
	}
}

状态模式(行为型)

一个对象有状态变化 每次状态变化都会触发一个逻辑 不能总是 if-else 控制

state 和 本体抽离出来 状态的切换是状态这个对象上的,状态的获取是主体这个对象上的

红绿灯

// 状态
class State {
	constructor(color) {
		this.color = color
	}
	handle(context) {
		console.log(this.color)
		context.setState(this)
	}
}
// 主体
class Context {
	constructor() {
		this.state = null
	}
	getState() {
		return this.state
	}
	setState(state) {
		this.state = state
	}
}
// 应用
let context = new Context()
let yellow = new State('yellow')
let red = new State('red')
let green = new State('green')

// 绿灯亮了
green.handle(context) // green
console.log(context.getState()) // 状态 State{color: 'green'}

场景 有限状态机(javascript-state-machine) 写一个简单的 promise promise 三种状态(pending, fullfilled, rejected) pending --> fullfilled pending --> rejected 不可逆向变化

// npm install 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() {
			console.log('收藏成功')
			updateText()
		},
		// 监听取消收藏
		onDeleteStore: function() {
			console.log('取消收藏')
			updateText()
		},
	},
})

let btn = document.getElementById('btn1')
// 按钮点击事件
btn.onClick = () => {
	if (fsm.is('收藏')) {
		fsm.doStore()
	} else {
		fsm.deleteStore()
	}
}
// 更新按钮文案
function updateText() {
	btn.innerHTML = fsm.state
}

// 初始化文案
updateText()

/*
promise
*/
promise是一个class
promise的参数是一个函数
传人的函数有两个参数resolve,reject
返回的promise有一个then方法

// 状态机模型
let fsm = new StateMachine({
	init: 'pending', // 初始化状态
	transitions: [
		{
			name: 'resolve', // 事件名称
			from: 'pending',
			to: 'fullfilled',
		},
		{
			name: 'reject',
			from: 'pending',
			to: 'rejected',
		},
	],
	methods: {
		// state状态机的实例,data传的参数, data - fsm.resolve(xxx) data就是xxx
		onResolve: function(state, data) {
			data.succesList.forEach(fn => fn())
		},
		onReject: function() {
			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.succesList.push(successFn)
		this.failList.push(failFn)
	}
}
// 应用
function loadImg(src) {
	return new MyPromise(function(resolve, reject) {
		let img = document.createElement('img')
		img.onload = function() {
			resolve(img)
		}
		img.onerror = function() {
			reject()
		}
		img.src = src
	})
}
let src = ''
let result = loadImg(src)
result.then(data => {
	console.log('data')
}, error => {
	console.log('error')
})

其他模式

场景

综合实例

购物车UML类图