观察者模式
三要素(绑定on 触发emit 解绑off)
观察者模式(Observer Pattern)是一种软件设计模式,它定义了一种一对多的依赖关系,使得多个对象之间的状态同步更新。在观察者模式中,一个被观察者对象(也称为主题对象)维护一个观察者列表,并在自身状态发生改变时,自动通知所有观察者进行更新。
观察者模式的核心思想是将对象间的依赖关系解耦。通常情况下,对象之间的依赖关系是直接耦合的,当一个对象的状态发生改变时,需要通知其他依赖该对象的对象进行更新,这样会使得代码变得复杂且难以维护。而观察者模式通过引入一个中介者(也称为主题对象),将对象间的依赖关系进行解耦,使得每个对象都只需要与主题对象进行交互,而不需要知道其他对象的存在。
在观察者模式中,通常会定义两个接口:一个被观察者接口和一个观察者接口。被观察者接口通常包含注册、注销和通知观察者的方法,观察者接口则包含接收主题对象通知的方法。具体实现可以根据实际情况进行扩展。
下面是一个示例代码:
class Observer {
constructor() {
// 全局对象,以便 存储 type 与 回调函数的关系
this.oTypes = {
// xxx: [f2]
};
}
// 绑定
on(type, callback) {
// 假设 type 第一次 出现在 this.oTypes 中,即 this.oTypes[type] 返回值是 undefined
if (!this.oTypes[type]) {
// 表示 type 第一次 出现在 this.oTypes 中
this.oTypes[type] = [callback];
} else {
// 表示 type 第 >= 2次 出现在 this.oTypes 中,this.oTypes[type] 的返回值 肯定是 数组
this.oTypes[type].push(callback)
}
}
// 触发; 注:data 是可选参
emit(type, ...data) {
// this.oTypes[type] 的返回值 只会是 两种类型:
// 1. undefined 表示 type 不存在于 this.oTypes
// 2. 数组,表示 type 存在于 this.oTypes,且 数组中都是 回调函数
if (Array.isArray(this.oTypes[type])) {
// 只要执行本代码块,即表示 type 存在于 this.oTypes,且 数组中都是 回调函数
this.oTypes[type].forEach(fn => fn(...data))
}
}
// 解绑
off(type, callback) {
// 加 判断的原因:若 this.oTypes[type] 的返回值不是数组,根本就弹出上,将 callback 从数组中 移除
if (Array.isArray(this.oTypes[type])) {
//
this.oTypes[type] = this.oTypes[type].filter(fn => {
return !(fn === callback);
// 注:fn === callback 返回值 若为 true,则 表示 此时的 fn 的要被移除掉
});
}
}
}
const f1 = () => {
console.log('f1')
}
const f2 = (a, b, c) => {
console.log('f2', a, b, c)
}
const target = new Observer();
target.on('xxx', f1);
target.on('xxx', f2);
setTimeout(() => {
// 表示 2S 之后 触发 绑定到 `xxx` 上的 回调函数
target.emit('xxx', '111', '222', '333', '444');
}, 2000);
策略模式
减少if/else 的使用
策略模式(Strategy pattern)是一种行为设计模式,它允许在运行时选择算法的行为。该模式定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。使用策略模式可以使算法的变化独立于使用算法的客户端,从而更灵活、可维护和可扩展。
在 JavaScript 中,策略模式可以通过创建一个策略对象来实现。该对象定义了一个公共接口,它封装了一系列具体的算法实现。然后,客户端代码可以选择不同的策略对象来使用不同的算法。
以下是一个示例代码:
// 设计 type 的值是后端返回的数据:
// 前后端 约定:
// `a` 表示 `未完成`
// `b` 表示 `已完成`
// `c` 表示 `进行中`
// 注:这种约定的好处:节省网络传输资源
// 需求:前端 根据 type 的值,进行alert 相关的 文字
const type = null;
/* 普通写法:
if (type === 'a') {
alert('未完成');
} else if (type === 'b') {
alert('已完成');
} else if (type === 'c') {
alert('进行中');
} else { // 强调:条件分支 一定要考虑 **极端/边界 情况**:即 不给 前端 `a/b/c` 的值,而是其他
alert('温馨提示:这不是 前端的锅...');
}
*/
// 代码 如何 更加 `可读`;
// -> 尽量地 减少 条件分支;(尤其是 嵌套的条件分支);
// 用策略模式,去除 上述的 条件分支:
const dict = {
// 声明一个函数, 名字为 `a`
// 代码块为 alert 的代码
a() {
alert('未完成');
},
b() {
alert('已完成');
},
c() {
alert('进行中');
},
// 即 非 `a/b/c` 的情况,则走本 函数
default() {
alert('温馨提示:这不是 前端的锅...');
}
};
// 拆解下面的代码:
// 1. dict[type] 的返回值 是 函数 声明
// 2. `dict[type]()` 表示 函数执行
// 3. 若 dict[type] 的返回值为 undefined,则 表示 type 不符合 `a/b/c` 的要求,则需要走 default 函数
(dict[type] || dict.default)();
工厂模式
用来创建对象的一种模式
JavaScript中的工厂模式是一种创建对象的设计模式,通过该模式可以创建多个相似的对象。工厂模式是一种简单而又实用的设计模式,它可以将对象的创建和实现分离开来,使得代码更加灵活,易于维护和扩展。
工厂模式的核心思想是定义一个用于创建对象的工厂函数,该函数接收一些参数,根据这些参数创建一个新的对象并返回。这种方式可以隐藏对象的创建细节,将其封装在一个函数内部,使得调用者只需要知道如何使用这个函数来创建对象,而不需要了解对象的具体实现细节
下面是一个简单的工厂模式的示例:
function createPerson(name, age) {
const person = {
name,
age,
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
};
return person;
}
const person1 = createPerson('Alice', 30);
const person2 = createPerson('Bob', 25);
person1.sayHello(); // Output: Hello, my name is Alice and I am 30 years old.
person2.sayHello(); // Output: Hello, my name is Bob and I am 25 years old.
在上面的示例中,createPerson函数是一个工厂函数,它接收两个参数 name 和 age,然后创建一个新的对象 person,并将这两个参数作为对象的属性,最后返回这个新创建的对象。通过调用 createPerson 函数来创建两个不同的 person 对象,然后可以通过调用对象上的 sayHello 方法来打印出相应的问候语。
工厂模式的优点是可以简化对象的创建过程,并提高代码的可读性和可维护性。同时,工厂模式也允许我们根据需要动态地创建对象,而不需要事先知道对象的具体实现细节。
单例模式
全局共享一个实例对象
在 JavaScript 中,单例模式是一种常用的设计模式,它可以保证一个类只有一个实例,并且提供全局访问点。
实现单例模式有多种方式,其中比较常见的方式是使用闭包或者静态属性来实现。
单例模式的 典型应用场景:
场景1:
比如:网页版QQ的登录框, 第一次 渲染DOM元素, 若后续还需要显示 该 登录框
不需要再次渲染;只需将 第一渲染的 DOM元素,重新 显示即可。
场景2:Dialog
注:Dialog 由两部分组成:
1)遮罩(背景灰色 - 作用:让用户专注于 弹窗)
2)弹窗容器(注:容器内 可以放置 任意的 其他的 DOM元素)
总结:Dialog 是典型的「单例模式」;即页面刷新,即 渲染了 Dialog 的相关DOM元素
在需要其显示的时候,再让其 显示。
场景3:
本页面 执行 import {add} from './add.js'; ,本质是 将 add 函数 放入 内存中。
其他页面,也执行 上述的代码;并不需要再将 add函数 放入内存中,其他页面 可以共用 同一 add的内存。
上述,也叫 「单例模式」
场景4:ES5 之前的 window
也是 典型的 「单例模式」。
使用闭包实现单例模式:
const Singleton = (function() {
let instance;
function createInstance() {
const object = new Object('I am the instance');
return object;
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
在上面的代码中,我们使用立即执行函数创建一个闭包,将实例对象保存在闭包内部的变量 instance 中。在 getInstance 方法中,首先判断实例对象是否已经存在,如果存在则直接返回该实例对象,否则通过调用 createInstance 方法创建一个新的实例对象。
使用静态属性实现单例模式:
class Singleton {
static instance = null;
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
在上面的代码中,我们使用静态属性 instance 来保存实例对象,如果 instance 已经存在,则返回该实例对象,否则通过 new 关键字创建一个新的实例对象并保存在 instance 中。
无论使用闭包还是静态属性,都可以实现单例模式,它们的核心思想都是利用闭包或静态属性保存实例对象,从而保证一个类只有一个实例,并且提供全局访问点。
总结:单例模式的作用:但凡是 只需要 一次声明/创造/渲染... ,那么 就不需要重复地 声明/创造...
原型模式
在 JavaScript 中,每个对象都有一个原型(prototype)属性,它指向另一个对象,这个对象就是当前对象的父对象。原型模式就是利用这个特性,实现对象之间的继承。
具体来说,当我们创建一个对象时,如果没有指定它的原型,则它的原型会默认指向 Object.prototype,即它继承了 Object 对象的所有属性和方法。如果我们希望创建一个自定义的对象,并让它继承一些其他对象的属性和方法,就可以通过设置它的原型来实现。
例如,我们可以定义一个 Person 类,并设置它的原型为一个 Animal 对象,这样 Person 对象就可以继承 Animal 对象的属性和方法。代码如下:
function Animal() {
this.type = 'animal';
}
Animal.prototype.say = function() {
console.log('I am a ' + this.type);
}
function Person(name) {
this.name = name;
}
Person.prototype = new Animal();
var person = new Person('Bob');
person.say(); // 输出:I am a animal
在上面的代码中,我们定义了一个 Animal 类,它有一个 type 属性和一个 say 方法。然后定义了一个 Person 类,它有一个 name 属性。最后将 Person 类的原型设置为一个 Animal 对象,这样 Person 对象就可以继承 Animal 对象的所有属性和方法,包括 type 属性和 say 方法。最后创建一个 Person 对象,并调用它的 say 方法,输出结果为“I am a animal”。
通过原型模式,我们可以轻松实现对象之间的继承,并实现代码的复用。
迭代器模式
在 JavaScript 中,迭代器模式是一种用于遍历集合中元素的设计模式。它可以使我们以一种简单、统一的方式访问集合中的元素,而不必关心集合的内部实现方式。
具体来说,迭代器模式包括两个核心部分:迭代器对象和可迭代对象。迭代器对象负责遍历可迭代对象中的元素,而可迭代对象则包含了一些可供遍历的元素。
在 JavaScript 中,迭代器对象通常是一个包含 next() 方法的对象,每次调用 next() 方法可以返回集合中的下一个元素。而可迭代对象通常是一个实现了 Symbol.iterator 方法的对象,它返回一个迭代器对象。
下面是一个简单的迭代器模式的例子:
const numbers = [1, 2, 3, 4, 5];
const iterator = numbers[Symbol.iterator]();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: 5, done: false}
console.log(iterator.next()); // {value: undefined, done: true}
在上面的代码中,我们创建了一个包含 5 个元素的数组 numbers,然后获取了它的迭代器对象 iterator,通过调用 next() 方法,我们可以遍历整个数组中的元素。
迭代器模式可以帮助我们简化代码,使其更加模块化和可复用。在实际开发中,它通常用于处理大量数据的情况下,以及需要对数据进行遍历和过滤的情况下。
代理模式
通过代理对象间接操作对象,实现数据拦截
const model = {
name: 'zhangsan',
age: 18
}
function render(data) {
return document.querySelector('#app').innerHTML = `
<div>${data.name}</div>
<div>${data.age}</div>
<div>${data.sex}</div>
`
}
function observer(model,render){
return new Proxy(model,{
get(target, key, receiver) {
if (Reflect.has(target, key)) {
return Reflect.get(target, key, receiver);
}
Reflect.set(target,key,'default',receiver);
renderFn(target);
return '默认值';
},
set(target, key, value, receiver) {
console.log(`setter...${value}`);
Reflect.set(target, key, value, receiver)
renderFn(target);
},
has(target, key) {
// if(!key in target){
// return '默认值';
// }
if (!Reflect.has(target, key)) {
Reflect.set(target, key, '默认值');
renderFn(target);
return Reflect.set(target, key, '默认');
}
}
})
}