前言
今天我们分享下四种常见的设计模式:观察者模式、发布订阅者模式、单例模式、工厂模式,设计模式学习的是一种思想,没有最好,只有更好
观察者模式
栗子:c 是某公司的领导,a 和 b 都加了 c 的联系方式,一天 c 发布了一个职位并且公布了职位的年龄标准,a 和 b 同时都能接收到,并且知道了自己是否通过
class FrontendPost {
subs = [];
constructor(name, age) {
this.name = name;
this.age = age;
}
subscribe(target, sub) {
target.subs.push(sub);
}
publish(value) {
for (const sub of this.subs) {
sub(value);
}
}
}
const a = new FrontendPost('a', 18);
const b = new FrontendPost('b', 28);
const c = new FrontendPost('c', 20);
// 需要去订阅 c
a.subscribe(c, (value) => {
if (a.age <= value) {
console.log('我是 a,I passed');
} else {
console.log('我是 a,I failed');
}
});
// 需要去订阅 c
b.subscribe(c, (value) => {
if (b.age <= value) {
console.log('我是 b,I passed');
} else {
console.log('我是 b,I failed');
}
});
// 最后由 c 来发布自己的事件
c.publish(c.age);
应用场景
- vue2 的响应式原理应用的就是观察者模式(讲解一下响应式的步骤,涉及观察者模式的地方代码讲解一下)
-
存在 Vue 类,首先会去 new Vue 创建实例,Vue 类里面:
- 使用 Observer 类劫持了 data 中的数据,也就是使用 Object.defineProperty (vue3 使用 proxy),拦截数据中的 get/set
- 使用 Compiler 类解析模板中的 vue 指令,将数据渲染到模板中,最后挂载到节点上,Compiler 类中使用了 CompilerUtil 类,在其中进行 Watcher 的实例化
-
下面讲一下 Watcher 类:
- Watcher 是 Observer 和 Compiler 的桥梁,帮助 Observer 收集依赖,触发 Compiler 更新函数
-
看下 Observer 、Watcher、 Dep(观察者模式中的收集依赖的作用) 类中的代码
- Observer 数据劫持
class Observer { constructor(data) { this.observer(data); } observer(obj) { if (obj && typeof obj === 'object') { for (let key in obj) { this.defineRecative(obj, key, obj[key]); } } } defineRecative(obj, attr, value) { this.observer(value); // 创建了属于当前属性的依赖收集实例 let dep = new Dep(); Object.defineProperty(obj, attr, { get() { // 在这里收集依赖 Dep.target && dep.addSub(Dep.target); return value; }, set: (newValue) => { if (value !== newValue) { this.observer(newValue); value = newValue; // 通知更新 dep.notify(); } }, }); } }
- Dep 类:收集依赖,观察者模式中的 subs 数组和这边的 subs 一个作用
class Dep { constructor() { this.subs = []; } addSub(watcher) { this.subs.push(watcher); } notify() { this.subs.forEach((watcher) => watcher.update()); } }
- Watcher 类:这边做了
Dep.target = this
指向自己的实例,收集好实例,然后再将实例释放
class Watcher { constructor(vm, attr, cb) { this.vm = vm; this.attr = attr; this.cb = cb; this.oldValue = this.getOldValue(); } getOldValue() { Dep.target = this; let oldValue = CompilerUtil.getValue(this.vm, this.attr); Dep.target = null; return oldValue; } update() { let newValue = CompilerUtil.getValue(this.vm, this.attr); if (this.oldValue !== newValue) { this.cb(newValue, this.oldValue); } } }
-
关键代码列出来了,分析一波:
- 首先 Observer 劫持所有 data 中的数据,
- 对于每个属性,会使用 Dep 创建属于每个属性的依赖收集实例,
- 在 get(劫持) 方法中将 Watcher 实例推进 subs 数组,
- 在 set(劫持) 方法中调用 Dep 创建实例的 notify 方法,也就是遍历 subs 数组中的实例,并且调用实例的 update 方法,更新视图
- 显而易见,我们创建了对象上某个属性的实例,并且将依赖
当前属性
的所有 Watcher 实例(所有使用相同指令的不同节点)推进 subs 数组,所以是观察者模式
发布订阅者模式
栗子:a 和 b 都关注了某个平台的某个职位并且开启了提醒,当 c 上线该平台公布了该岗位的年龄要求之后,a 和 b 都会收到通知,并且知道自己是否通过
//调度中心
class Topic {
static subs = {};
static subscribe(key, sub) {
if (!this.subs[key]) {
this.subs[key] = [];
}
this.subs[key].push(sub);
}
static publish(key, value) {
if (!this.subs[key]) return;
for (const sub of this.subs[key]) {
sub(value);
}
}
}
class FrontendPost {
constructor(name, age) {
this.name = name;
this.age = age;
}
subscribe(key, sub) {
Topic.subscribe(key, sub);
}
publish(key, value) {
Topic.publish(key, value);
}
}
const a = new FrontendPost('a', 18);
const b = new FrontendPost('b', 28);
const c = new FrontendPost('c', 38);
// 向调度中心注册 post 事件
a.subscribe('post', (value) => {
if (a.age <= value) {
console.log('我是 a,I passed');
} else {
console.log('我是 a,I failed');
}
});
// 向调度中心注册 post 事件
b.subscribe('post', (value) => {
if (b.age <= value) {
console.log('我是 b,I passed');
} else {
console.log('我是 b,I failed');
}
});
c.publish('post', c.age);
观察者模式和发布订阅者模式的差别
- 发布订阅者模式和观察者模式最大的差别在于:
发布订阅者模式
有一个事件调度中心 - 发布订阅模式的耦合比较松,观察者模式的耦合比较紧
单例模式
- 限制类只能有一个实例
- 提供为全局可访问
- 减少不必要的开销
- 全局变量
let _instance = null
function getSingle(){
if (!_instance){
_instance = this
}
return _instance
}
let m1 = new getSingle()
let m2 = new getSingle()
console.log(m1 === m2) //true
- 使用闭包
const getSingle = (function () {
let _instance = null;
function getInstance() {}
return function () {
if (!_instance) {
_instance = new getInstance();
}
return _instance;
};
})();
let a = getSingle();
let b = getSingle();
console.log(a === b);
- 类的静态属性
class GetSingle {
static instance = '21';
static getInstance() {
if (!this.instance) {
this.instance = new GetSingle();
}
return this.instance;
}
}
let a = GetSingle.getInstance();
let b = GetSingle.getInstance();
console.log(a === b);
应用场景
- 引入第三方库(多次引用,只会使用一次库的引用)
- Vue 框架中生使用的 new Vue 就应用了单例模式
- 一个全局使用的类频繁地被创建和销毁,占用内存,比如 Modal、Loading
工厂模式(简单工厂模式)
- 创建对象
- 工厂模式就是创建对象的一种方式
- 创建对象,降低代码冗余度
- 当你想要批量生产同类型的对象的时候
function Idiot(name, age) {
this.name = name;
this.age = age;
}
const a = new Idiot('a', 18);
const b = new Idiot('b', 28);
console.log(a, b);
- 不同类的实例化
class Football {
name = 'football';
isChineseTeam() {
console.log('of course not');
}
}
class Basketball {
name = 'basketball';
constructor(name) {
this.name = name;
}
isChineseTeam() {
console.log('there may be');
}
}
// ball 工厂
const BallFactory = function (name) {
switch (name) {
case 'NBA':
return new Basketball();
case 'worldCup':
return new Football();
}
};
const football = new BallFactory('worldCup');
console.log(football);
football.isChineseTeam();
安全模式类
- 出现以下情况,忘记加 new 关键字
function Idiot(name, age) {
this.name = name;
this.age = age;
}
Idiot.prototype.say = () => {
console.log('i am idiot');
};
const a = new Idiot('a', 18);
const b = Idiot('b', 28);
a.say();
b.say();
- 改造,做一个兼容,加上 new 关键字
function Idiot(name, age) {
if (!(this instanceof Idiot)) {
return new Idiot(name, age);
}
this.name = name;
this.age = age;
}
Idiot.prototype.say = () => {
console.log('i am idiot');
};
const a = new Idiot('a', 18);
const b = Idiot('b', 28);
a.say();
b.say();
抽象工厂模式
- 抽象类是一种声明式的类,使用时候报错,定义子类应该具备的方法,如果子类没有需要报错,只用于继承
- 定义类中必备的方法,子类未重写则报错
class Ball {
constructor() {
if (new.target === Ball) {
throw new Error('抽象类不能被实例化');
}
}
isChineseTeam() {
throw new Error('需要重写');
}
}
class Football extends Ball {
name = 'football';
isChineseTeam() {
console.log('of course not');
}
}
class Basketball extends Ball {
name = 'basketball';
constructor(name) {
this.name = name;
}
isChineseTeam() {
console.log('there may be');
}
}
// ball 工厂
const BallFactory = function (name) {
switch (name) {
case 'NBA':
return new Basketball();
case 'worldCup':
return new Football();
}
};
const football = new BallFactory('worldCup');
console.log(football);
football.isChineseTeam();
后续
当我们去看某些框架的源码的时候,不应当仅仅为了面试,我们应当去看到它们的优秀可取之处,拓展自己的思维,应该学习到优秀的框架设计,而不是仅仅又会了一道面试题而已,共勉。