常见的设计模式在JavaScript中的应用:单例模式、工厂模式、组合模式、观察者模式、代理模式
单例模式
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
- 构建一个函数,返回一个对象实例
- 用一个声明一次的变量来控制这个对象实例的生成
- 如果这个变量里面已经存了一个对象则直接返回,如果没有则生成一个对象
闭包实现
function singleClosure() { let obj; return function () { if (!obj) { obj = new Object(); } return obj; }; }
原型实现
function singlePrototype() { if (!Object.prototype.instance) { Object.prototype.instance = new Object(); } return Object.prototype.instance; }
static实现
function singleStatic() { if (!Object.instance) { Object.instance = new Object(); } return Object.instance; }
全局变量实现
不推荐,因为window对象只有浏览器有,非浏览器的global(全局对象)不是window
function singleWindow() { if (!window.instance) { window.instance = new Object(); } return window.instance; }
工厂模式
工厂模式:以一个工厂方法来生产对应的对象。
1.手动构建对象
2.手动给对象设置属性
3.手动返回对象
function factory() { let obj = new Object(); obj.name = 'jack'; obj.age = '18'; return obj; }
组合模式*
组合多个对象形成树形结构以表示“整体-部分”的关系的层次结构。组合模式对叶子节点和容器节点的处理具有一致性,又称为整体-部分模式,使得用户对单个对象和组合对象的使用具有一致性。
将多个相同名字的方法,放在一个地方统一调用,比如下面创建了三个类,他们的实例对象都包含say方法
class SayHello { constructor() {} say() { console.log('hello'); } } class SayHi { constructor() {} say() { console.log('hi'); } } class SayGoodBye { constructor() {} say() { console.log('good bye'); } }
以上的三个类分别都具备一个say方法,如果需要调用的话那么是用一个个的对象分别进行调用,而不能统一调用,如果我需要他统一调用,这个时候我们就可以使用组合模式。
class Combiner { constructor() { this.objs = []; } push(obj) { this.objs.push(obj); } excute(fnName) { this.objs.forEach((item) => { item[fnName](); }); } } let combiner = new Combiner(); combiner.push(new SayHello()); combiner.push(new SayHi()); combiner.push(new SayGoodBye()); combiner.excute('say'); //打印:hello hi good bye
Vue中的use方法就是用的组合模式实现,该方法是用来安装vue插件,该方法内部会集中调用插件提供的install方法。下面是简单的代码模拟:
class Vue { constructor() { //容器来保存对应的对象 this.objs = []; } use(obj) { this.objs.push(obj); this.excute(); } excute() { //执行对应的方法 this.objs.forEach((item) => { item['install'](); }); } }
观察者模式*
观察者模式(obServer)他又被称为发布-订阅者模式,消息模式等。它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
事件就是一种观察者模式
1.事件监听
2.处理函数执行(js引擎)
3.取消事件监听
// 观察者应该具备三个的对应的方法(1.发布事件 2.执行处理函数 3.取消事件) class ObServer { constructor() { //事件和对应的处理函数的存储容器 this.arg = {}; //{click:[fn1,fn2]} } //监听事件 参数:事件名,处理函数 on(eventName, fn) { if (!this.arg[eventName]) { this.arg[eventName] = []; } this.arg[eventName].push(fn); } // 执行处理函数 emit(eventName, params) { if (!this.arg[eventName]) { return; } // 将里面的处理函数都执行 this.arg[eventName].forEach((fn) => { // 将参数传入执行 fn.call(this, params); }); } // 取消事件 off(eventName, fn) { if (this.arg[eventName].length == 1) { delete this.arg[eventName]; } else { for (let i; i < this.arg[eventName].length; i++) { if (Object.is(this.arg[eventName][i], fn)) { this.arg[eventName].splice(i, 1); break; } } } } } let obServer = new ObServer(); obServer.on('click', function (param) { console.log('执行了点击' + param); }); function handlerClick(param) { console.log('二次处理点击' + param); } obServer.on('click', handlerClick); obServer.off('click', handlerClick); obServer.emit('click', 'hello');
在emit方法传入参数传到对应的on里面的处理函数,vue里面子组件传值给父组件实现就是利用了这个原理,儿子通过emit去传值,父亲通过on去接收值
代理模式*
所谓代理模式是指并不直接调用实际的对象,而是通过调用代理对象,来间接的调用实际的对象。通俗的来讲代理模式就是我们生活中常见的中介。
es7新增了一个类叫 Proxy 他就是用于代理的,可以通过Proxy来访问目标对象的属性和方法,可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。它是vue3的底层实现。
// 目标对象 let target = { name: '张三', age: 18 }; // 利用proxy生成代理对象 let proxy = new Proxy(target, { //参数:目标对象 属性名 代理对象 get(target, property, proxy) { //访问值的时候调用 if (property == 'name') { return '我的名字是' + target[property]; } if (property == 'age') { return '我的年纪是' + target[property]; } }, //参数:目标对象 属性名 属性值 代理对象 set(target, property, value) { // 设置值的时候调用 target[property] = value; }, deleteProperty(target, property, proxy) { // 删除属性的时候调用 delete target[property]; }, has(target, property) { // 的时候调用 必须返回boolean类型 console.log('has调用了'); return property in target; }, apply(target, property) { // 函数调用的时候触发 }, }); console.log(proxy.age); //调用get proxy.name = 'jack'; //调用set console.log(proxy); console.log('name' in proxy); //调用has 返回boolean值 delete proxy.name; //调用deleteProperty console.log(proxy);
适配器模式
又叫做变压器模式,就是用来做适配,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
let phone = { fn() { return '5v'; }, };
class Target { constructor() {} fn() { let v = phone.fn(); return '200转换为' + v; } } new Target().fn();