工厂模式
创建对象的一种方式,不用每次亲自创建对象,而是通过一个既定的“工厂”来生产对象。
OOP 中,默认创建对象一般是 new class ,但一些情况下用 new class 会很不方便。
let f1;
class Foo {}
if (a) f1 = Foo(x);
if (b) f2 = Foo(x, y);
此时就需要一个“工厂”,把创建者和 class 分离,符合开放封闭原则。
function create(a, b) {
if (a) return Foo(x);
if (b) return Foo(x, y);
}
const f1 = create(a, b);
示例
class Product {
name: string;
constructor(name: string) {
this.name = name;
}
make() {}
process() {}
}
const create = (name: string): Product => {
return new Product(name);
};
常见场景
<div>
<span>静态文字</span>
<span :id="hello" class="bar">{{ msg }}</span>
</div>
编译出 _createXxx JS 代码,这些就是使用工厂函数创建 vnode 。
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("span", null, "静态文字"),
_createElementVNode("span", {
id: _ctx.hello,
class: "bar"
}, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["id"])
]))
}
const profile = <div>
<img src="avatar.png" className="profile" />
<h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>
编译之后
// 返回 vnode
const profile = React.createElement("div", null,
React.createElement("img", { src: "avatar.png", className: "profile" }),
React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);
其实React.createElement也是一个工厂。
class Vnode(tag, attrs, children) {
// ...省略内部代码...
}
React.createElement = function (tag, attrs, children) {
return new Vnode(tag, attrs, children)
}
如果 JQuery 开放给用户的不是$,而是new JQuery(selector);
带来的问题:不方便链式操作、不宜将构造函数暴露给用户、尽量高内聚、低耦合。
declare interface Window {
$: (selector: string) => JQuery;
}
class JQuery {
selector: string;
length: number;
constructor(selector: string) {
// 使用 slice 因为 NodeList 不是数组,没有数组的方法
const domList = Array.prototype.slice.call(
document.querySelectorAll(selector)
);
const length = domList.length; // 获取长度
for (let i = 0; i < length; i++) {
this[i] = domList[0];
}
this.selector = selector;
this.length = length;
}
append(elem: HTMLElement): JQuery {
// ...
return this;
}
addClass(key: string, value: string): JQuery {
// ...
return this;
}
}
window.$ = (selector) => {
return new JQuery(selector);
};
单例模式
即对一个 类 只能创建一个实例,即使无论调用多少次。
例如 Vuex redux 这些全局数据存储,全局只能有一个实例。
class Store { /* get set ... */ }
const store1 = new Store()
store1.set(key, value)
const store2 = new Store()
store2.get(key) // 获取不到
示例
利用 TS 特性
class SingletonDesign {
private constructor() {} // 外部无法初始化
private static instance: SingletonDesign | null; // 静态属性,用于存储实例
static getInstance(): SingletonDesign {
if (SingletonDesign.instance == null) {
SingletonDesign.instance = new SingletonDesign(); // 第一次调用时创建实例
}
return SingletonDesign.instance; // 返回实例
}
}
const s1 = SingletonDesign.getInstance()
const s2 = SingletonDesign.getInstance()
console.log(s1 === s2) // true
使用闭包
function genGetInstance() {
let instance; // 闭包
class Singleton {}
return () => {
if (instance == null) {
instance = new Singleton();
}
return instance;
};
}
const getInstance = genGetInstance();
const s1 = getInstance();
const s2 = getInstance();
console.log(s1 === s2); // true
结合模块化语法
let instance: null | Single = null;
class Single {}
const initSingle = () => {
if (instance === null) {
instance = new Single();
}
return instance;
};
export default initSingle;
观察者模式
前端最常用的一个设计模式,也是 UI 编程最重要的思想;
例如你点奶茶,此时你并不需要在吧台坐等,等做好了服务员会叫你。
场景
- 定时器
- Promise
- Vue React 的生命周期
- Vue 组件更新过程
DOM 事件
const $btn = $("#btn");
$btn.click(function () {
console.log("click");
});
Vue watch
{
data() {
name: 'zhangsan'
},
watch: {
name(newVal, val) {
console.log(newValue, val)
}
}
}
Nodejs stream
const fs = require('fs')
const readStream = fs.createReadStream('./file.txt')
let length = 0
readStream.on('data', function (chunk) {
length += chunk.toString().length
})
readStream.on('end', function () {
console.log(length)
})
Nodejs readline
const readline = require('readline');
const fs = require('fs')
const rl = readline.createInterface({
input: fs.createReadStream('./data/file1.txt')
})
let lineNum = 0
rl.on('line', function(line){
lineNum++
})
rl.on('close', function() {
console.log('lineNum', lineNum)
})
MutationObserver
<div id="container">
<p>A</p>
<p>B</p>
</div>
function callback(records: MutationRecord[], observer: MutationObserver) {
for (let record of records) {
console.log('record', record)
}
}
const observer = new MutationObserver(callback)
const containerElem = document.getElementById('container')
const options = {
attributes: true, // 监听属性变化
attributeOldValue: true, // 变化之后,记录旧属性值
childList: true, // 监听子节点变化(新增删除)
characterData: true, // 监听节点内容或文本变化
characterDataOldValue: true, // 变化之后,记录旧内容
subtree: true, // 递归监听所有下级节点
}
// 开始监听
observer.observe(containerElem!, options)
// 停止监听
// observer.disconnect()
代码模拟
// 被观察者
class Subject {
private state: string = "init";
private observers: Observer[] = []; // 观察者列表
getState() {
return this.state;
}
setState(newState: string) {
this.state = newState;
this.notify(); // 通知所有观察者
}
// 添加观察者
attach(observer: Observer) {
this.observers.push(observer);
}
// 通知所有观察者
private notify() {
for (const observer of this.observers) {
observer.update(this.state);
}
}
}
// 观察者
class Observer {
name: string;
constructor(name: string) {
this.name = name;
}
update(state: string) {
console.log(`${this.name} update, state is ${state}`);
}
}
const sub = new Subject(); // 被观察者
const observer1 = new Observer("A");
const observer2 = new Observer("B");
sub.attach(observer1);
sub.attach(observer2);
sub.setState("done"); // 更新状态,触发观察者 update
发布订阅模式
观察者模式的另一个版本,用于实现对象之间的松耦合通信;
在该模式中,存在一个或多个发布者(Publishers)和一个或多个订阅者(Subscribers);
发布者负责发布消息,而订阅者负责订阅感兴趣的消息并在接收到消息时做出相应的处理;
如 postMessage、Nodejs 多进程通讯、WebWorker
对比观察者模式
观察者模式 中间无媒介 如 addEventListener 绑定事件
发布订阅模式 中间有媒介 如 event 自定义事件
场景
自定义事件 Vue2 实例本身支持,Vue3 推荐使用 mitt
import mitt from 'mitt'
const emitter = mitt() // 单例
export default emitter
emitter.on('change', () => {
console.log('change')
})
emitter.emit('change')
mitt 没有 once ,推荐 event-emitter
import eventEmitter from 'event-emitter' // 安装TS类型声明 @types/event-emitter
const emitter = eventEmitter()
emitter.once('change', (value: string) => {
console.log('change', value)
})
emitter.emit('change', '张三')
组件销毁之前及时 off 避免内存泄漏
created() {
emitter.on('change', this.fn)
},
beforeUnmount() {
emitter.off('change', this.fn)
}
代码模拟
class PubSub {
subscribers = {} // 存放订阅者
/**
* 订阅
* @param eventName 事件名称
* @param callback
*/
on(eventName, callback) {
if (!this.subscribers[eventName]) {
this.subscribers[eventName] = [] // 初始化
}
this.subscribers[eventName].push(callback)
}
/**
* 取消订阅
* @param eventName 事件名称
* @param callback
*/
off(eventName, callback) {
if (this.subscribers[eventName]) {
// 订阅函数的索引
const index = this.subscribers[eventName].findIndex(item => item === callback)
if (index !== -1) {
this.subscribers[eventName].splice(index, 1)
}
}
}
// 发布
emit(eventName, data) {
if (this.subscribers[eventName]) {
this.subscribers[eventName].forEach(callback => {
callback(data)
})
}
}
}
const pubsub = new PubSub()
function callback1(data) {
console.log('订阅1收到', data)
}
function callback2(data) {
console.log('订阅2收到', data)
}
function callback3(data) {
console.log('订阅3收到', data)
}
pubsub.on('message', callback1) // 订阅消息
pubsub.on('advertisement', callback2) // 订阅广告
pubsub.on('notice', callback2) // 订阅通知
pubsub.emit('message', '今天天气状况不错,适合出游') // 发布消息
pubsub.emit('advertisement', '全场5折') // 发布广告
pubsub.emit('notice', '明天放假一天') // 发布通知
pubsub.off('message', callback2) // 取消订阅
装饰器模式
允许向一个现有的对象添加新的功能,同时又不改变其结构;
这种类型的设计模式属于结构型模式,作为现有的类的一个包装,动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
简单地说: 允许向一个现有的对象添加新的功能,同时又不改变其结构,目标是分离和解耦的;
例如,手机上套一个壳可以保护手机,壳上粘一个指环,这就是一种装饰
function decorate(phone) {
phone.fn3 = function () {
console.log('指环')
}
}
const phone = {
name: 'iphone12',
fn1() {}
fn2() {}
}
const newPhone = decorate(phone)
class Circle {
draw() {
console.log('画一个圆')
}
}
class Decorator {
private circle: Circle
constructor(circle: Circle) {
this.circle = circle
}
draw() {
this.circle.draw()
this.setBorder()
}
private setBorder() {
console.log('设置边框颜色')
}
}
const circle = new Circle()
circle.draw()
const decorator = new Decorator(circle)
decorator.draw()
ES 引入了 Decorator 语法,tsconfig.json 配置 experimentalDecorators: true
// 装饰器
function testable(target: any) {
target.isTestable = true
}
@testable
class Foo {
static isTestable?: boolean
}
console.log(Foo.isTestable) // true
也可以进行传参
// 装饰器工厂函数
function testable(val: boolean) {
// 装饰器
return function (target: any) {
target.isTestable = val
}
}
@testable(false)
class Foo {
static isTestable?: boolean
}
console.log(Foo.isTestable) // false
装饰类
/**
* @param instance 实例
* @param key 属性名
*/
function instanceDecorator(instance: InstanceType<any>, key: any) {}
/**
* @param target 实例
* @param key 属性名
* @param descriptor 属性描述符
*/
function readOnly(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.writable = false
}
// 传参
function configurable(val: boolean) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.configurable = val
}
}
class PropertyDescribe {
@instanceDecorator // 等同于 instanceDecorator(this, "_name");
_name = '张三'
_age = 20
// 等同于 Object.defineProperty(this, "getName", { writable: false });
@readOnly
getName() {
return this._name
}
// 等同于 Object.defineProperty(this, "getAge", { configurable: false });
@configurable(false)
getAge() {
return this._age
}
}
const pdd = new PropertyDescribe()
// @ts-ignore
console.log(Object.getOwnPropertyDescriptor(pdd.__proto__, 'getName'))
// @ts-ignore
console.log(Object.getOwnPropertyDescriptor(pdd.__proto__, 'getAge'))
同样 react-redux 也采用了装饰器模式
import { connect } from 'react-redux'
export default TodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList)
import { connect } from 'react-redux'
@connect(mapStateToProps, mapDispatchToProps)
export default TodoList extends React.Component { }
AOP 面向切面编程实现 log 打印
function log(target: any, key: string, descriptor: PropertyDescriptor) {
console.log(descriptor);
const oldValue = descriptor.value; // business 函数
console.log(oldValue);
// 重新定义 business 函数
descriptor.value = function () {
console.log(`记录日志...`);
return oldValue.apply(this, arguments);
};
}
class Business {
@log // 不影响业务功能的代码,只加 log 的 “切面”
business() {
console.log("业务功能");
}
}
const bs = new Business();
bs.business();
代理模式
为其他对象提供一种代理,以控制对这个对象的访问。
直接访问对象时带来的问题:
在面向对象系统中,有些对象由于某些原因(如对象创建开销大,或某些操作需要安全控制,或需要进程外的访问等等...),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上对此对象的访问层。
例如,你通过房产中介买房子,中介就是一个代理。
通过代理类在调用真实类之前做一些处理
class RealImg {
fileName;
constructor(fileName) {
this.fileName = fileName;
}
display() {
console.log("display...", this.fileName);
}
}
// 大致原理:通过代理类,在调用真实类之前做一些处理
class IProxy {
readImg;
constructor(fileName) {
this.readImg = new RealImg(fileName);
}
display() {
// dome something
this.readImg.display();
}
}
// 使用代理
const proxImg = new IProxy("proxy.png");
proxImg.display();
场景 - 代理模式在前端十分很常用,诸如以下例子:
DOM 事件代理
<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
</div>
<button>点击增加一个 a 标签</button>
<script>
var div1 = document.getElementById('div1')
div1.addEventListener('click', function (e) {
var target = e.target
if (e.nodeName === 'A') {
alert(target.innerHTML)
}
})
</script>
// webpack.config.js
module.exports = {
// 其他配置...
devServer: {
proxy: {
'/api': 'http://localhost:8081',
},
},
};
nginx 反向代理
server {
listen 8000;
location / {
proxy_pass http://localhost:8001;
}
location /api/ {
proxy_pass http://localhost:8002;
proxy_set_header Host $host;
}
}
Vue3 reactive
function reactive(target = {}) {
// 不是对象或数组
if (typeof target !== 'object' || target === null) return target
// 代理配置项
const observed = new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
return reactive(result) // 深度监听
},
set(target, key, val, receiver) {
if (val === target[key]) return true // 判重
return Reflect.set(target, key, val, receiver)
},
deleteProperty(target, key) {
return Reflect.deleteProperty(target, key)
}
})
return observed
}
const data = {
realName: 'zhangsan'
}
const user = reactive(data)
user.realName = 'lisi'
console.log(user.realName) // lisi
职责链模式
顾名思义,就是一步操作可能分位多个职责角色来完成,把这些角色都分开,然后用一个链串起来。这样就将请求者和处理者、包括多个处理者之间进行了分离;
前端最常见的就是链式操作。
如:promise.then、Jquery 链式
Query 链式操作
$("#div1").show().css("color", "red").append($("#p1"));
Promise 链式操作
function loadImg(src: string) {
const promise = new Promise((resolve, reject) => {
const img = document.createElement("img");
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject("图片加载失败");
};
img.src = src;
});
return promise;
}
const src = "//www.baidu.com/img/flexible/logo/pc/result.png";
const result = loadImg(src);
result
.then((img: HTMLImageElement) => {
console.log("img.width", img.width);
return img;
})
.then((img: HTMLImageElement) => {
console.log("img.height", img.height);
})
.catch((err) => {
console.log(err);
});
策略模式
主要解决多个 if...else 或者 switch...case 的问题,每种情况分成多种策略,分别实现。
class User {
private type: string
constructor(type: string) {
this.type = type
}
buy() {
const { type } = this
if (type === 'ordinary') {
// ...
}
if (type === 'member') {
// ...
}
if (type === 'vip') {
// ...
}
}
}
const u1 = new User('ordinary')
u1.buy()
const u2 = new User('member')
u2.buy()
const u3 = new User('vip')
u3.buy()
使用策略模式
interface IUser {
buy: () => void
}
class OrdinaryUser implements IUser {
buy() {
// ...
}
}
class MemberUser implements IUser {
buy() {
// ...
}
}
class VipUser implements IUser {
buy() {
// ...
}
}
const u1 = new OrdinaryUser()
u1.buy()
const u2 = new MemberUser()
u2.buy()
const u3 = new VipUser()
u3.buy()
适配器模式
我们需要一个对象的 API 提供能力,但它的格式不一定完全适合我们的格式要求。这就要转换一下
如 Vue computed
{
data() {
return {
userList: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
]
}
},
computed: {
userNameList() {
this.userList.map(user => user.name)
}
}
}
MVC
- View 传送指令到 Controller
- Controller 完成业务逻辑后,要求 Model 改变状态
- Model 将新的数据发送到 View,用户得到反馈
MVVM
MVVM 对标 Vue 即可,View 和 Model 通过 viewModel 实现互动
- View 即 Vue template(DOM / 模板)
- Model 即 Vue data(vue 组件中的 data 或者vuex )
- VM 即 Vue 其他核心功能(连接层、可以理解为Vue实例中的监听、指令、方法等),负责 View 和 Model 通讯
View 通过事件监听等修改 Model,Model 通过指令修改 View