Javascript 设计模式
面向对象
什么是面向对象
-
概念
-
三要素:继承 封装 多态
-
JS的应用举例
-
面向对象的意义
概念-类
class People {
constructor(name, age) {
this.name = name
this.age = age
}
eat() {
alert(`${this.name} eat something`)
}
speak() {
alert(`${this.age} speak`)
}
}
三要素
-
继承,子类继承父类
-
封装, 数据的权限和保密
-
多态,同一接口不同实现
三要素 - 继承
-
People 是父类,公共的,不仅仅服务于Student
-
继承可将公共方法抽离出来,提高复用,减少冗余
// 子类继承父类
class Student extends People {
constructor(name, age, number) {
super(name, age)
this.number = number
}
study() {
alert(`${this.name} study`)
}
}
三要素 - 封装
-
public 完全开放
-
protected 对子类开放
-
private 对自己开放
-
(ES6尚不支持,可以用typescript来掩饰)
// 父类
class People {
name
age
protected weight // 定义protected 属性
constructor(name, age) {
this.name = name
this.age = age
this.weight = 120
}
study() {
alert(`${this.name} study`)
}
}
// 子类
class Student extends People {
number
private girlfriend // 定义 private属性
constructor(name, age, number) {
super(name, age)
this.number = number
this.girlfriend = 'xiaoli'
}
}
-
减少耦合,不该外露的不外露
-
利于数据、接口的权限管理
-
ES6目前不支持,一般认为_开头的属性是private
三要素 - 多态
-
同一个接口,不同表现
-
JS应用极少
-
需要结合java等语言的接口、重写、重载等功能
-
保持子类的开放性和灵活性
-
面向接口编程
-
(JS引用极少, 了解即可)
JS 应用举例
-
jQuery 是一个class
-
$('p') 是jQuery的一个实例
为何使用面向对象?
-
程序执行:顺序、判断、循环 - 结构化
-
面向对象 - 数据结构化
-
对于计算机,结构化的才是最简单的
-
编程应该 简单 & 抽象
UML类图
-
Unified Modeling Language 统一建模语言
-
类图,UML包含很多种图,和本科相关的是类图
-
关系,主要讲解泛化和关联
-
演示,代码和类图结合
画图工具
-
MS Office visio
类图
+ public 属性名A:类型
# protected属性名B:类型
- private 属性名C:类型
+ public 方法名:(参数1,参数2):返回值类型
# protected 方法名:(参数1,参数2):返回值类型
- private 方法名:(参数1,参数2):返回值类型
关系
-
泛化,表示继承
-
关联,表示引用
设计原则
-
何为设计?
-
五大设计原则
-
从设计到模式
-
介绍23种设计模式
何为设计?
-
描述
-
结合 《UNIX/LINUX设计哲学》
描述
-
即按照哪一种思路或者标准来实现功能
-
功能相同,可以有不同设计方案来实现
-
伴随着需求增加,设计的作用才能体现出来
《UNIX/LINUX设计哲学》
-
准则1:小即是美
-
准则2:让每个程序只做好一件事
-
准则3:快速建立原型
-
准则4:舍弃高效率而取可移植性
-
准则5:采用纯文本来存储数据
-
准则6:充分利用软件的杠杆效应(软件复用)
-
准则7:使用shell脚本来提高杠杆效应和可移植性
-
准则8:避免强制性的用户界面
-
准则9:让每个程序都称为过滤器
-
小准则:允许用户定制环境
-
小准则:尽量使操作系统内核小而轻量化
-
小准则:使用小写字母并尽量简短
-
小准则:沉默是金
-
小准则:各部分之和大于整体
-
小准则:寻求90%的解决方案
SOLID 五大设计原则
-
S - 单一职责原则
-
O - 开放封闭原则
-
L - 李氏置换原则
-
I - 接口独立原则
-
D - 依赖导致原则
S - 单一职责原则
-
一个程序只做好一件事
-
如果功能过于复杂就拆分开,每个部分保持独立
O - 开放封闭原则
-
对扩展开放,对修改封闭
-
增加需求时,扩展新代码,而非修改已有代码
-
这是软件设计的终极目标
L - 李氏置换原则
-
子类能覆盖父类
-
父类能出现的地方子类就能出现
-
JS 中使用较少 (弱类型 & 继承使用较少)
I - 接口独立原则
-
保持接口的单一独立,避免出现 "胖接口"
-
JS中没有接口(typescript例外),使用较少
-
类似于单一职责原则,这里更关注接口
D - 依赖倒置原则
-
面向接口编程,依赖于抽象而不依赖于具体
-
使用方只关注接口而不关注具体类的实现
-
JS中使用较少(没有接口 & 弱类型)
设计原则总结
-
S O 体现较多,详细介绍
-
L I D 体现较少,但是要了解其用意
用promise来说明S O
-
单一职责原则:每个 then中的逻辑只做好一件事
-
开放封闭原则:如果新增需求,扩展then
-
对扩展开放,对修改封闭
23种设计模式
-
创建型
-
组合型
-
行为型
创建型
-
工厂模式 (工厂方法模式,抽象工厂模式,建造者模式)
-
单例模式
-
原型模式
结构型
-
适配器模式
-
装饰器模式
-
代理模式
-
外观模式
-
桥接模式
-
组合模式
-
享元模式
行为型
-
策略模式
-
模板方法模式
-
观察者模式
-
迭代器模式
-
职责连模式
-
命令模式
-
备忘录模式
-
状态模式
-
访问者模式
-
中介者模式
-
解释器模式
面试题示例
第一题
-
打车时,可以打专车或者快车。任何车都有车牌号和名称
-
不同车价格不同,快车每公里1元,专车每公里2元。
-
行程开始时,显示车辆信息
-
行程结束时,显示打车金额(假定行程5公里)
-
画出UML类图
-
用ES6语法写出该示
第二题
-
某停车场,分3层,每层100车位
-
每个车位都能监控到车辆的驶入和离开
-
车辆进入前,显示每层的空余车位数量
-
车辆进入时,摄像头可识别车牌号和时间
-
车辆出来时,出口吸纳时期显示车牌号和停车时长
工厂模式
-
将new 操作单独封装
-
遇到 new 时,就要考虑是否该使用工厂模式
示例
-
你去购买汉堡,直接点餐、取餐,不会自己亲手做
-
商店要 "封装"做汉堡的工作,做好直接给买者
设计原则验证
-
构造函数和创建者分离
-
符合开放封闭原则
单例模式
介绍
-
系统中被唯一使用
-
一个类只有一个实例
场景
-
jQuery 只有一个$
-
模拟登录框
-
vuex和redux中的store
设计原则验证
-
符合单一职责原则,只实例化唯一的对象
-
没法具体开放封闭原则,但是绝对不违反开放封闭原则
适配器模式
介绍
-
旧接口格式和使用者不兼容
-
中间加一个适配器转换接口
场景
-
封装旧接口
-
vue computed
装饰器模式
介绍
-
为对象添加新功能
-
不改变其原有的结构和功能
场景
-
ES7 装饰器
-
core-decorators
ES7装饰器
-
装饰类
-
装饰方法
// 装饰类
@testDec
class Demo {
// ...
}
function testDec(target) {
target.isDec = true;
}
alert(Demo.isDec) // true
// 装饰类 有参数
function testDec(isDec) {
return function (target) {
target.isDec = isDec
}
}
@testDec(false)
class Demo {
}
alert(Demo.isDec)
function mixins(...list) {
return function (target) {
Object.assign(target.prototype, ...list)
}
}
const Foo = {
foo() { alert('foo') }
}
@mixins(Foo)
class MyClass{}
let obj = new MyClass()
obj.foo() // 'foo'
function readonly(target, name, descriptor) {
descriptor.writable = false
return descriptor
}
class Person {
constructor() {
this.first = 'A'
this.last = 'B'
}
@readonly
name() {
return `${this.first} ${this.last}`
}
}
function log(target, name, descriptor) {
let oldValue = descriptor.value
descriptor.value = function () {
console.log(`calling ${name} width`, arguments)
return oldValue.apply(this, arguments)
}
return descriptor
}
class Math {
@log
add(a, b) {
return a + b
}
}
let math = new Math()
const result = math.add(2, 4)
console.log(result)
core-decorators
-
第三方开源lib
-
提供常用的装饰器
import { readonly } from 'core-decorators'
class Person {
@readonly
name() {
return 'zhang san'
}
}
let p = new Person()
alert(p.name())
p.name = function () {} // 报错
import { deprecate } from 'core-decorators'
class Person {
@deprecate('即将废除', {url: 'www.baidu.com'})
name() {
return 'zhangsan'
}
}
let p = new Person()
p.name()
设计原则验证
-
将现有对象和装饰器进行分离,两者独立存在
-
符合开放封闭原则
代理模式
介绍
-
使用者无权访问目标对象
-
中间加代理,通过代理做授权和控制
示例
-
科学上网,访问github.com
-
明星经纪人
场景
-
网页事件代理
-
jQuery $.proxy
-
ES6 Proxy
网页事件代理
ES6 Proxy
// 明星
let star = {
name: '张XX',
age: 25,
phone: '13900001111'
}
// 经纪人
let agent = new Proxy(star, {
get: function (target, key) {
if (key === 'phone') {
// 返回经纪人自己的电话
return '16899997777'
}
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
console.log('agent.customPrice', agent.customPrice)
设计原则验证
-
代理类和目标类分离,隔离开目标类和使用者
-
符合开放封闭原则
代理模式 vs 适配器模式
-
适配器模式:提供一个不同的接口(如不同版本的插头)
-
代理模式:提供一模一样的接口
代理模式 vs 装饰器模式
-
装饰器模式:扩展功能,原有功能不变且可直接使用
-
代理模式:显示原有功能,但是经过限制或者阉割之后的
外观模式
介绍
-
为子系统中的一组接口提供了一个高层接口
-
使用者使用这个高层接口
观察者模式
介绍
-
发布 & 订阅
-
一对多
示例
- 点咖啡,点好之后坐等被叫
场景
-
网页事件绑定
-
Promise
-
jQuery callbacks
-
nodejs 自定义事件
-
nodejs中:处理http请求;多进程通讯
-
vue和React 组件生命周期触发
-
vue watch
jQuery callbacks
// 自定义事件, 自定义函数
var callbacks = $.Callbacks() //注意大小写
callbacks.add(function (info) {
console.log('fn1', info)
})
callbacks.add(function (info) {
console.log('fn1', info)
})
callbacks.add(function (info) {
console.log('fn1', info)
})
callbacks.fire('gogogo')
callbacks.fire('fire')
nodejs 自定义事件
const EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter()
// 监听 some 事件
emitter1.on('some', info => {
console.log('fn1', info)
})
// 监听 some 事件
emitter1.on('some', info => {
console.log('fn2', info)
})
// 触发 some 事件
emitter1.emit('some', 'xxxxx')
设计原则验证
-
主题和观察者分离,不是主动触发而是被动监听,两者解耦
-
符合开放封闭原则
迭代器模式
介绍
-
顺序访问一个集合
-
使用者无需知道集合的内部结构(封装)
场景
-
jQuery each
-
ES6 Iterator
jQuery each
var arr = [1, 2, 3]
var nodeList = document.getElementsByTagName('a')
var $a = $('a')
function each(data) {
var $data = $(data) // 生成迭代器
$data.each(function (key, val) {
console.log(key, val)
})
}
each(arr)
each(nodeList)
each($a)
ES6 Iterator 为何存在?
-
ES6语法中,有序集合的数据类型已经有很多
-
Array Map Set String TypedArray arguments NodeList
-
需要有一个统一的遍历接口来遍历所有数据类型
-
(注意,object不是有序集合,可以用Map代替)
ES6 Iterator 是什么?
-
以上数据类型,都有[Symbol.iterator]属性
-
属性值是函数,执行函数返回一个迭代器
-
这个迭代器就有 next 方法可顺序迭代子元素
-
可运行 Array.prototype[Symbol.iterator]来测试
function each(data) {
// 生成遍历器
let iterator = data[Symbol.iterator]()
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
let item = {done: false}
while(!item.done) {
item = iterator.next()
if (!item.done) {
console.log(item.value)
}
}
}
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 100
each(arr)
function each2(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', 100
each2(arr)
ES6 Iterator 与 Generator
-
Iterator 的价值不限于上述几个类型的遍历
-
还有Generator 函数的使用
-
即只要返回的数据符合Iterator 接口的要求
-
即可使用Iterator语法,这就是迭代器模式
设计原则验证
-
迭代器对象和目标对象分离
-
迭代器将使用者与目标对象隔离开
-
符合开放封闭原则
状态模式
介绍
-
一个对象有状态变化
-
每次状态变化都会触发一个逻辑
-
不能总是用if...else来控制
场景
-
有限状态机
-
写一个简单的Promise
有限状态机
-
有限个状态、以及在这些状态之间的变化
-
如交通信号灯
-
使用开源 lib: javascript-state-machine
import StateMachine from 'javascript-state-machine'
import $ from 'jquery'
// 初始化状态机模型
let fsm = new StateMachine({
init: '收藏',
transitions: [
{
name: 'doStore',
from: '收藏',
to: '取消收藏'
},
{
name: 'deleteStore',
from: '取消收藏',
to: '收藏'
}
],
methods: {
// 监听执行收藏
onDoStore: function () {
alert('收藏成功') // 可以 post 请求
updateText()
},
// 监听取消收藏
onDeleteStore: function () {
alert('已经取消收藏') // 可以 post 请求
updateText()
}
}
})
let $btn = $('#btn1')
// 按钮点击事件
$btn.click(function () {
if (fsm.is('收藏')){
fsm.doStore()
} else {
fsm.deleteStore()
}
})
// 更新按钮的文案
function updateText() {
$btn.text(fsm.state)
}
// 初始化文案
updateText()
写一个简单的Promise
Promise 就是有限状态机
-
Promise 三种状态:pending fullfilled rejected
-
pending -> fullfilled 或者 pending -> rejected
-
不能逆向变化
import StateMachine from 'javascript-state-machine'
// 模型
let 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) 传递的参数
},
// 监听 reject
onReject: function (state, data) {
// state - 当前状态机实例;data - fsm.reject(xxx) 传递的参数
}
}
})
// 定义 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 Promise(function (resolve, reject) {
let img = document.createElement('img')
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject()
}
img.src = src
})
return promise
}
let src = 'xxxx.jpg'
let result = loadImg(src)
result.then(function (img) {
console.log('ok1')
}, function () {
console.log('fail1')
})
result.then(function (img) {
console.log('ok2')
}, function () {
console.log('fail2')
})
其他设计模式
优先级划分依据
-
不常用
-
对应不到经典实现方式
原型模式
概念
-
clone 自己,生成一个新对象
-
java 默认有clone接口,不用自己实现
JS中的应用 - Object.create
// 一个原型对象
let prototype = {
getName: function () {
return this.first + ' ' + this.last
},
say: function () {
alert('hello')
}
}
// 基于原型创建 x
let x = Object.create(prototype)
x.first = 'A'
x.last = 'B'
alert(x.getName())
x.say()
// 基于原型创建 y
let y = Object.create(prototype)
y.first = 'C'
y.last = 'D'
alert(y.getName())
y.say()
对比JS中的原型 prototype
-
prototype 可以理解为ES6 class的一种底层原理
-
而class 是实现面向对象的基础,并不是服务于某个模式
-
若干年后ES6 全面普及,大家可能会忽略掉prototype
-
但是Object.create 却会长久存在
桥接模式
概念
-
用于把抽象化与实现化解耦
-
使得二者可以独立变化
组合模式
概念
-
生成树形结构,表示 "整体-部分" 关系
-
让整体和部分都具有一致的操作方式
享元模式
概念
-
共享内存(主要考虑内存,而非效率)
-
相同的数据,共享使用
策略模式
概念
-
不同策略分开处理
-
避免出现大量 if ... else 或者 switch...case
模板方式模式和职责连模式
职责连模式-概念
-
一步操作可能分为多个职责角色来完成
-
把这些角色都分开,然后用一个链串起来
-
将发起者和各个处理者隔离
JS中的链式操作
-
职责链模式和业务结合较多,JS中能联想到链式操作
-
jQuery的链式操作 Promise.then 的链式操作
命令模式
概念
-
执行命令时,发布者和执行者分开
-
中间加入命令对象,作为中转站
备忘录模式
概念
-
随时记录一个对象的状态变化
-
随时可以恢复之前的某个状态(如撤销功能)
-
未找到JS中经典应用,除了一些工具(如编辑器)
中介者模式
class A {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setB()
}
}
}
class B {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setA()
}
}
}
// 中介者
class Mediator {
constructor(a, b) {
this.a = a
this.b = b
}
setA() {
let number = this.b.number
this.a.setNumber(number * 100)
}
setB() {
let number = this.a.number
this.b.setNumber(number / 200)
}
}
// 测试
let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(100, m)
console.log(a.number, b.number)
b.setNumber(100, m)
console.log(a.number, b.number)
访问者模式
-
数据操作和数据结构进行分离
-
使用场景不多
解释器模式
-
描述语言语法如何定义,如何解释和编译
-
用于专业场景
关于面试
- 能说出重点讲解的设计模式即可