过长的if else-if
//优化前
howManyDays(year, month){
if(month === 1 ||
month === 3 ||
month === 5 ||
month === 7 ||
month === 8 ||
month === 10 ||
month === 12
){
return 31
}else if(month === 2){
return isLeapYear(year) ? 29 : 28
}else{
return 30
}
}
//优化后
howManyDays(year, month){
const table = {
1: 31, 3: 31, 5: 31, 7: 31, 8: 31, 10: 31, 12:31,
4: 30, 6:30, 9: 30, 11: 30,
2: isLeapYear(year) ? 29 : 28
}
return table[month]
}
//优化前
function calculateGrade(score){
if(score>=90){
return 'A'
}else if(score >= 80){
return 'B'
}else if(score >= 70){
return 'C'
}else if(score >= 60){
return 'D'
}else {
return 'E'
}
}
//优化后
function calculateGrade(score){
const table = {
100: 'A',
90: 'A',
80: 'B',
70: 'C',
60: 'D',
others: 'E'
}
return table[Math.floor(score/10)*10] || table['others']
}
策略/状态模式
策略模式基本结构:减少了if…else的数量,提升了代码的可读性,通过传入一个状态来选取具体的策略
假如我们需要做一个计算器,需要支持加减乘除,为了判断用户具体需要进行哪个操作,我们需要4个if…else来进行判断,如果支持更多操作,那if…else会更长,不利于阅读,看着也不优雅。所以我们可以用策略模式优化如下:
function calculator(type, a, b) {
const strategy = {
add: function(a, b) {
return a + b;
},
minus: function(a, b) {
return a - b;
},
division: function(a, b) {
return a / b;
},
times: function(a, b) {
return a * b;
}
}
return strategy[type](a, b);
}
// 使用时
calculator('add', 1, 1);
状态模式基本架构
function stateFactor(state) {
const stateObj = {
status: '',
state: {
state1: function(){},
state2: function(){},
},
run: function() {
return this.state[this.status];
}
}
stateObj.status = state;
return stateObj;
}
// 使用时
stateFactor('state1').run();
外观模式(常用)
外观模式基本结构:将一个个小模块封装成更高级的接口内部,传给外部一个简单的接口
当我们设计一个模块时,里面的方法可以会设计得比较细,但是暴露给外面使用的时候,不一定非得直接暴露这些细小的接口,外部使用者需要的可能是组合部分接口来实现某个功能,我们暴露的时候其实就可以将这个组织好。
function model1() {}
function model2() {}
// 可以提供一个更高阶的接口,组合好了model1和model2给外部使用
function use() {
model2(model1());
}
实例:常见的接口封装
外观模式说起来其实非常常见,很多模块内部都很复杂,但是对外的接口可能都是一两个,我们无需知道复杂的内部细节,只需要调用统一的高级接口就行,比如下面的选项卡模块:
// 一个选项卡类,他内部可能有多个子模块
function Tab() {}
Tab.prototype.renderHTML = function() {} // 渲染页面的子模块
Tab.prototype.bindEvent = function() {} // 绑定事件的子模块
Tab.prototype.loadCss = function() {} // 加载样式的子模块
// 对外不需要暴露上面那些具体的子模块,只需要一个高级接口就行
Tab.prototype.init = function(config) {
this.loadCss();
this.renderHTML();
this.bindEvent();
}
迭代器模式
迭代器模式基本结构:*当后端传来大量结构相似的数据时,js的数组并不能很好的处理这些数据时,我们可以自己按照需求封装迭代器功能
迭代器模式模式在JS里面很常见了,数组自带的forEach就是迭代器模式的一个应用,我们也可以实现一个类似的功能:
function Iterator(items) {
this.items = items;
}
Iterator.prototype.dealEach = function(fn) {
for(let i = 0; i < this.items.length; i++) {
fn(this.items[i], i);
}
}
备忘录模式
备忘录模式基本结构:**加一个缓存对象,来记录之前获取过的数据或者操作的状态,后面可以用来加快访问速度或者进行状态回滚
备忘录模式类似于JS经常使用的缓存函数,内部记录一个状态,也就是缓存,当我们再次访问的时候可以直接拿缓存数据,可以用其实现操作的前进后退功能:
function memo() {
const cache = {};
return function(arg) {
if(cache[arg]) {
return cache[arg];
} else {
// 没缓存的时候先执行方法,得到结果res
// 然后将res写入缓存
cache[arg] = res;
return res;
}
}
桥接模式
桥接模式人如其名,其实就相当于一个桥梁,把不同维度的变量桥接在一起来实现功能。假设我们需要实现三种形状(长方形,圆形,三角形),每种形状有三种颜色(红色,绿色,蓝色),这个需求有两个方案,一个方案写九个方法,每个方法实现一个图形:
function redRectangle() {}
function greenRectangle() {}
function blueRectangle() {}
function redCircle() {}
function greenCircle() {}
function blueCircle() {}
function redTriangle() {}
function greenTriangle() {}
function blueTriangle() {}
使用桥接模式后,我们可以观察重复代码拆成多个维度,再将这些维度拼接起来
function rectangle(color) { // 长方形
showColor(color);
}
function circle(color) { // 圆形
showColor(color);
}
function triangle(color) { // 三角形
showColor(color);
}
function showColor(color) { // 显示颜色的方法
}
// 使用时,需要一个红色的圆形
let obj = new circle('red');
享元模式
当我们观察到代码中有大量相似的代码块,他们做的事情可能都是一样的,只是每次应用的对象不一样,我们就可以考虑用享元模式。现在假设我们有一个需求是显示多个弹窗,每个弹窗的文字和大小不同:
// 已经有一个弹窗类了
function Popup() {}
// 弹窗类有一个显示的方法
Popup.prototype.show = function() {}
如果我们不用享元模式,一个一个弹就是这样:
var popup1 = new Popup();
popup1.show();
var popup2 = new Popup();
popup2.show();
使用享元模式后:
var popupArr = [
{text: 'popup 1', width: 200, height: 400},
{text: 'popup 2', width: 300, height: 300},
]
var popup = new Popup();
for(var i = 0; i < popupArr.length; i++) {
popup.show(popupArr[i]); // 注意show方法需要接收参数
}
模板方法模式
模板方法模式其实类似于继承,就是我们先定义一个通用的模板骨架,然后后面在这个基础上继续扩展。我们通过一个需求来看下他的基本结构,假设我们现在需要实现一个导航组件,但是这个导航类型还比较多,有的带消息提示,有的是横着的,有的是竖着的,而且后面还可能会新增类型:
// 先建一个基础的类
function baseNav() {
}
baseNav.prototype.action = function(callback){} //接收一个回调进行特异性处理
实例:弹窗
还是之前用过的弹窗例子,我们要做一个大小文字可能不同的弹窗组件,只是这次我们的弹窗还有取消和确定两个按钮,这两个按钮在不同场景下可能有不同的行为,比如发起请求什么的。但是他们也有一个共同的操作,就是点击这两个按钮后弹窗都会消失,这样我们就可以把共同的部分先写出来,作为一个模板:
function basePopup(word, size) {
this.word = word;
this.size = size;
this.dom = null;
}
basePopup.prototype.init = function() {
// 初始化DOM元素
var div = document.createElement('div');
div.innerHTML = this.word;
div.style.width = this.size.width;
div.style.height = this.size.height;
this.dom = div;
}
// 取消的方法
basePopup.prototype.cancel = function() {
this.dom.style.display = 'none';
}
// 确认的方法
basePopup.prototype.confirm = function() {
this.dom.style.display = 'none';
}
工厂模式
**封装的模块就像一个工厂一样批量的产出需要的对象。常见工厂模式的一个特征就是调用的时候不需要使用new,而且传入的参数比较简单。但是调用次数可能比较频繁,经常需要产出不同的对象,频繁调用时不用new也方便很多。一个工厂模式的代码结构如下所示:
function factory(type) {
switch(type) {
case 'type1':
return new Type1();
case 'type2':
return new Type2();
case 'type3':
return new Type3();
}
}
// 我们传入了type,然后工厂根据不同的type来创建不同的对象
实例: 弹窗组件
我们项目需要一个弹窗,弹窗有几种:消息型弹窗,确认型弹窗,取消型弹窗,他们的颜色和内容可能是不一样的,工厂模式改造:
// 新加一个方法popup把这几个类都包装起来
function popup(type, content, color) {
switch(type) {
case 'infoPopup':
return new infoPopup(content, color);
case 'confirmPopup':
return new confirmPopup(content, color);
case 'cancelPopup':
return new cancelPopup(content, color);
}
}
// 调用方法
let infoPopup1 = popup('infoPopup', content, color);
改造成面向对象
上述代码虽然实现了工厂模式,但是switch始终感觉不是很优雅。我们使用面向对象改造下popup,将它改为一个类,将不同类型的弹窗挂载在这个类上成为工厂方法:
function popup(type, content, color) {
// 如果是通过new调用的,返回对应类型的弹窗
if(this instanceof popup) {
return new this[type](content, color);
} else {
// 如果不是new调用的,使用new调用,会走到上面那行代码
return new popup(type, content, color);
}
}
// 各种类型的弹窗全部挂载在原型上成为实例方法
popup.prototype.infoPopup = function(content, color) {}
popup.prototype.confirmPopup = function(content, color) {}
popup.prototype.cancelPopup = function(content, color) {}
参考链接https://blog.csdn.net/m0_52009348/article/details/122395937