前言
目前没精力深入了解,所以先简单记录一些比较常用的设计模式。
日后有空了,待我深入了解后再更新文章~
工厂模式
大白话来说就是,定义了一个函数,这个函数相当于一个工厂,你需要什么样的产品,就输入什么样的参数,最终它给你生产出来。
例如:
- React createElement函数
- JQ的$()
- 你自己封装的构造函数
拿构造函数来说,例如你封装了很多可以归为一个大类的class:
class IntNum {...}
class AntNum {...}
class TelNum {...}
// ... 等等
咱们可以把这堆关于不同类型数据处理的类,归纳成一个构造函数:
function GetTypeNumClass(type) {
// ... 根据不同类型,return一个new class
}
这就是一个最简单的工厂模式写法
单例模式
主要特点是只能生成全局的唯一实例,并且提供一个能访问他的全局访问点。
例如:
- vuex的store
面试可能会让你用es5实现,我这里贴出一个实现方式,如果不是奔着面试可以不用记:
var Test = (function () {
var instance = null;
return function (name) {
if (instance) {
return instance;
}
this.name = name;
instance = this;
// return this;
};
})();
var a = new Test("a");
var b = new Test("a");
Test.prototype.say = function () {
alert(0);
};
var c = new Test("a");
console.log(a == b, b == c);
说明:利用了函数包裹立即执行创建了一个单独的作用域,并且将实例变量提升到最高层作用域上,最后在返回的函数中作为一个构造函数,里面吧this指向赋值给实例变量。
我们还是来看看真正在业务中要怎么使用,给个小例子,在TS中可以通过class加修饰符去写:
class SingleTon {
private static instance: SingleTon | null = null
private constructor() {}
public static getInstance(): SingleTon {
if (this.instance === null) {
this.instance = new SingleTon()
}
return this.instance
}
fn1() {}
fn2() {}
}
const a = SingleTon.getInstance()
a.fn1()
a.fn2()
const b = SingleTon.getInstance()
console.log(a === b); // 实例化出来的对象是同一个
- public:表示公有,在父类定义函数中和外,以及子类都能直接以任何形式访问。
- private:表示私有,只有在父类定义函数中访问,子类函数内部无法访问(但可通过外面调用继承的父类方法去访问)。
修饰符详细可以参考: 【TS基础】个人学习记录4-类的定义、继承、修饰符、静态、只读、多态、抽象
结合业务,例如我需要在一个应用中创建一个Websocket实例,供各个地方使用,但信息的收发都是必须由同一个Websocket实例来做,此时,单例模式就非常适用。我写个demo:
import { Socket } from "socket.io-client";
import io from "socket.io-client";
export class WebSocketSingleton {
private static instance: WebSocketSingleton;
private socket: Socket | null = null;
public isInitData: boolean = true; // 添加自定义属性
private constructor() {
this.socket = io(appConfig.DEV_RAM_AXIOS_BASE_URL);
// 监听数据
this.socket.on("message", (msg) => {
console.log(this.isInitData ? "-----init" : "-----update", msg);
// this.isInitData = false; 修改自定义属性
});
}
public static getInstance(): WebSocketSingleton {
if (!WebSocketSingleton.instance) {
WebSocketSingleton.instance = new WebSocketSingleton();
}
return WebSocketSingleton.instance;
}
// 发送数据
public sendMessage(data: any) {
if (this.socket) {
const json = JSON.stringify(data);
this.socket.emit("send_message", json);
}
}
}
在各个文件中可以这样使用:
// 引入省略...
const wsInstance = WebSocketSingleton.getInstance();
wsInstance.sendMessage(data);
在JavaScript这样的单线程环境下,实现单例模式相对容易。因为在任何时候,只有一个操作(一个线程)在运行,所以我们不需要担心多个线程同时去创建单例对象的问题。这意味着我们不需要加入额外的同步控制来保护单例创建的过程。
但是在多线程的环境下,比如Java,实现单例模式就要复杂一些了。在这样的环境下,可能存在多个线程同时尝试创建单例的情况,这会导致多个实例的产生,违反了单例模式只有一个实例的规则。为了防止这种情况,我们需要用到同步控制,例如使用synchronized关键字或者Lock接口,来确保在生成单例对象时只有一个线程能够执行,避免多实例的问题(GPT回答)。
代理模式
就是你想访问一个对象是无法直接访问的,只能通过一个代理层去访问。
例如:
- proxy,通过get和set的触发进行访问
观察者模式
例如:
- 平时加的监听器
addEventListener
MutationObserver
的使用。
举个业务中的例子,你需要监听一个DOM的变化,就可以用MutationObserver
观察者模式去处理。
它创建一个观察者实例,用来观察一个DOM元素,并在它或它的子元素发生变化时被通知。这是观察者模式的一个很好的实践。
MutationObserver(观察者)观察DOM元素(主题),并在元素发生变动时接收到通知。
以下是一个简单的使用示例:
// 创建一个观察者实例
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type); // 输出变动类型
});
});
// 配置观察选项:
const config = { attributes: true, childList: true, characterData: true }
// 在目标节点上开始观察:
observer.observe(target, config);
// 之后,你可以停止观察
// observer.disconnect();
在这个例子中,我们创建了一个观察者实例来观察目标节点的改变。当被观察的target发生变化时,观察者的回调函数会被触发并输出变动类型。
发布订阅模式
例如:
- vue的bus机制
和观察者模式的区别:
- 观察者模式,我认为触发主体和接收者是强绑定的,例如按钮和点击事件,二者关联性强。
- 发布订阅模式,我认为触发的主体和接收者无需强绑定,接收者只和事件总线强绑定。
装饰器模式
相当于给原来的一些东西新附上一些功能
例如:
- class和方法等的修饰符
- ts的decorator语法
尾巴
多了解一些设计模式,你才能设计出高级的代码。中级前端和高级前端之间相差的其中一个维度就是代码设计。