一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情。
设计模式,不是一种语法规范,而是前辈们在开发实践中的经验总结,是用于解决特定问题的某一套“模板”。使用设计模式,可提高代码的可扩展性和可维护性,一定程度上也能提高代码的健壮性。也许我们在平时的开发中,没有刻意使用设计模式,但是根据自己开发的经验和追求代码的质量,也会使用设计模式的思想,只不过没注意这就是设计模式。本文就简单介绍一下JavaScript常用的设计模式,注意在实现方式上,JavaScript和typescript是有所不同的。
前言
经典设计模式有23个,但是是基于后端写的,前端常用的没有23个。设计模式重在设计,也就是在实际开发代码前,应先想好要怎么写,而不是直接上手就写。例如怎么设计数据结构、如何合理拆分组件、如何封装通用API等。在探讨设计模式之前,需要明白设计模式的总原则:开闭原则,即扩展开放,修改关闭。对于面向对象来说,还有一个重要的里氏替换原则。设计模式在Java中体现的更为明显,本文列举前端常用的设计模式:工厂模式、单例模式、代理模式、观察者模式、发布订阅模式、装饰器模式。
工厂模式
一般我们在新建一个对象的实例时,都是使用new的方式创建。使用工厂模式,就是将创建实例的过程封装成统一的函数,并对外提供。在使用时,就不用关心内部实现,只需要调用函数即可。
举个栗子
// JavaScript也是支持面向对象的
class Person {}
function factory() {
return new Person();
}
const student = factory(); // 通过工厂函数实例化对象
使用场景
封装SDK、API
单例模式
全局唯一的实例,无法创建第二个实例,也就是单例。实现单例模式一般有两种方式:类和闭包。为什么类和闭包可以实现单例模式,先来看看什么是单例。顾名思义就是每次创建返回的都是同一个对象, 那么肯定就不能直接使用new的方式创建,而是需要提供一个函数,并在此函数执行时判断是否已经创建了实例,如果创建了就直接返回。类的静态属性是伴随类自身的,闭包是长驻内存的,因此这两种方式都可以实现单例模式。
类实现
class Single {
constructor() {}; // js中没有私有构造函数,区别于ts
static getInstance() { // 静态函数
if (!Single.instance) {
Single.instance = new Single(); // 第一次Single.instance为undefined,会实例化一个对象
}
return Single.instance; // 返回对象实例
}
}
let s1 = Single.getInstance(); // 不能通过new直接创建,只能通过静态方法getInstance获取
let s2 = Single.getInstance();
console.log(s1 === s2); // true
闭包实现
let Person = (function () {
let instance; // 定义变量
let PersonClass = function() {}; // 实例化对象也可借助函数,不一定非得用class
return function() {
if (!instance) {
instance = new PersonClass();
}
return instance;
}
})(); // 自执行函数
let p1 = new Person();
let p2 = new Person();
console.log(p1 === p2);
对比Java
- JavaScript无法私有化构造函数,Java需要指定私有构造函数
- JavaScript是单线程,创建单例不用考虑多线程;Java需要考虑多线程
- JavaScript无法私有静态属性
使用场景
代理模式
使用者无法直接访问对象,需要先访问一个中间代理层。然后在代理层,完成getter、setter等操作。ES6中新增的Proxy就是典型的代理模式
举个栗子
let Person = {
name: 'hello'
}
let proxy = new Proxy(Person, {
get: function(target, property) {
if (property in target) {
return target[property];
} else {
return undefined;
}
}
});
proxy.name; // hello
proxy.age; // undefined
使用场景
Vue3的响应式监听
观察者模式
观察一个主题,当主题发生变化时,观察者就执行对象的函数。常见的就是使用addEventListener监听事件变化。
window.addEventListener('click', () => {});
// 观察者:window
// 观察主题:click
// 主题发生变化时,触发函数
使用场景
使用addEventListener监听事件,需注意在使用完毕后要及时清理监听事件,避免内存泄漏
发布订阅模式
参考EventBus,发布者发布一个事件,已经订阅该事件的订阅者将收到此事件,并做出对应的处理。手写EventBus就不在本处涉及了,只是简单说明发布订阅模式的场景:
// 订阅
event.on('event', () => {});
// 发布
event.emit('event');
使用场景
Vue中的Bus事件总线,自定义EventBus,使用完自定义事件后要及时清理,避免内存泄漏
装饰器模式
装饰器是一种与类相关的语法,可用于修改或注释类和类方法,原功能保持不变,在此基础上新增一些功能。该模式还未正式确定,因此在使用时需要在jsconfig中配置experimentalDecorators:true。参考装饰器 - ECMAScript 6入门 (ruanyifeng.com)
装饰类
@testDecorator
class myDecorator {
// 类
}
function testDecorator(target) {
target.test = true;
}
console.log(myDecorator.test); // true
@testDecorator就是一个装饰器,修改了myDecorator类的属性,@testDecorator的参数就是myDecorator类自身。
装饰类方法
class Person {
@nonenumerable
get kidCount() { return this.children.length; }
}
function nonenumerable(target, name, descriptor) {
descriptor.enumerable = false;
return descriptor;
}
使用场景
使用装饰器的例子:nest.js、Mixin
总结
- 本文重点描述了前端,特别是JavaScript中的几种常用设计模式
- 装饰器还处于“提案”阶段,使用时需要配置
experimentalDecorators:true - 日常开发中要善于利用设计模式,先设计好,才能更好的开发
原创不易,转载请注明出处。