最近有时间看看书,看了JavaScript设计模式与开发实践这本书,之前草草看过这本书,已经忘的差不多了,很多关于设计模式的书都是基于类的,这本书基于javascript的语言特点讲了设计模式,把看到的实践总结一遍才会变成自己的。
单例模式
这应该是最简单的一种设计模式,在应用中我们通常需要一个单独的实例来实现一些功能,比如线程池,登录框,任务队列。
例如这样
function Single(name) {
this.name = name;
this.instance = null;
}
Single.getInstance = function(name) {
if (!this.instance) {
this.instance = new Single(name);
}
return this.instance;
};
var a = Single.getInstance("小明");
var b = Single.getInstance("小周");
console.log(a === b);
我们实现一个单例的方法还需要创建一个类,在执行一个类上的方法,这样写代码有些受java等面向对象语言的干扰,利用js的特性可以直接实现一个单例模式,全局变量
就是唯一的一个实例,但使用全局变量会引起变量污染问题,我们可以使用闭包来实现一个简单的单例
// 实现一个代理的通用方法
var single = (function() {
var instance;
return function getInstance(fn) {
if (!instance) {
instance = fn.apply(this, [].slice.call(arguments, 1));
}
return instance;
};
})();
// 单例模式具体执行的方法
var createDiv = function(html) {
var div = document.createElement("div");
div.innerHTML = html;
document.body.appendChild(div);
return div;
};
var div1 = single(createDiv, "single1");
var div2 = single(createDiv, "single2");
console.log(div1 === div2);
根据单一职责的实践,我们将负责单一实例的功能与具体执行的方法分开设计,这样保证了更好的通用性,和方便以后对方法进行修改。其实我们在一些常用的方法中已经用过这种模式,比如我们通常用来优化页面滚动的函数截流
和函数防抖
。都是在滚动的时候保证只有一个单例的方法去执行自定义的逻辑,防止函数被多次调用。
策略模式
我们在写代码时经常会写if else
逻辑判断的代码
例如年终评绩效时得 A 会发 4 倍工资,B 是 3 倍工资,C 是 2 倍工资
/**
* 获取年终奖金
* @param {string} performance // 绩效
* @param {number} wage 基本工资
*/
function getBouns(performance, wage) {
if (performance === "A") {
return 4 * wage;
} else if (performance === "B") {
return 4 * wage;
} else if (performance === "C") {
return 4 * wage;
}
}
这是很常见的代码编写,当逻辑比较少的时候这样写是最佳的,但当我们有很多判断逻辑,并且之后可能会频繁修改就需要用到策略模式了。下边是用传统方式实现的策略模式
// 下边是绩效类
function performanceA() {}
performanceA.prototype.caculate = function(wage) {
return 4 * wage;
};
function performanceB() {}
performanceB.prototype.caculate = function(wage) {
return 3 * wage;
};
function performanceC() {}
performanceC.prototype.caculate = function(wage) {
return 2 * wage;
};
/**
* 工资计算类
* @param {*} performance
* @param {*} wage
*/
function Bouns(performance, wage) {
this.performance = performance;
this.wage = wage;
}
Bouns.prototype.getBouns = function() {
return this.performance.caculate(this.wage);
};
var bouns = new Bouns(new performanceA(), 10000);
console.log(bouns.getBouns());
在 java中我们要实现任何方法都要依赖类来实现,通过类去实例化对象。我们想要针对不同的条件去执行不同的类,其实还是要ifelse
的判断。二期对于一个固定的对象我们还实现了构造函数,有些画蛇添足。在js中我们可以直接定义对象字面量,向下边这样
// 定义所有策略对象
var performances = {
A: function(wage) {
return wage * 4;
},
B: function(wage) {
return wage * 3;
},
C: function(wage) {
return wage * 2;
}
};
function getBouns(performance, wage) {
if (performances[performance]) {
return performances[performance](wage);
}
throw new Error("performance is not define");
}
var bouns = getBouns("A", 10000);
console.log(bouns);
这里直接使用一个对象来接收所有计算奖金的逻辑,我们的业务代码getBouns
在遇到修改比如我想加一个S的绩效不用改变,我们直接修改performances
这个承载所有计算逻辑的对象就可以实现业务的修改
这里再举一个例子,我们可以使用策略模式实现一个js动画,我们使用动画的时候经常会用css的动画,transtion
的属性有ease,linear
代表运动速率函数,left,width
代表要改变的css属性,我们可以使用策略模式来简单实现一个基于js的动画方法
// 计算运动的算法,4个参数是已消耗时间,原始位置,位置间的差距,持续的总时间
const motions = {
linear: function(t, b, c, d) {
return (c * t) / d + b;
},
ease: function(t, b, c, d) {
return c * (t /= d) * t + b;
}
};
/**
* 动画的主方法,模仿css的属性参数
*/
class Animate {
/**
*
* @param {*} dom 需要修改的dom元素
* @param {*} property 要改变的style属性
* @param {*} target 改变style的目标值
* @param {*} time 运动的时间
* @param {*} motion 运动函数
*/
constructor(dom, property, target, time, motion) {
this.dom = document.querySelector(dom);
this.property = property;
this.startDom = +getComputedStyle(this.dom)[property].replace("px", "");
this.startTime = +new Date();
this.curTime = this.startTime;
this.time = time;
this.end = this.startTime + time;
this.endDom = target;
this.motion = motions[motion];
}
/**
* 开始执行动画
*/
start() {
this.timer = setInterval(() => {
this.step();
}, 60);
}
// 每一步执行的具体步骤
step() {
this.curTime = +new Date();
if (this.curTime > this.end) {
clearInterval(this.timer);
this.update(this.endDom);
return;
}
var p = this.motion(
this.curTime - this.startTime,
this.startDom,
this.endDom - this.startDom,
this.time
);
this.update(p);
}
// 更新dom的方法
update(value) {
this.dom.style[this.property] = value + "px";
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#box{
background: red;
width: 100px;
height: 200px;
}
</style>
</head>
<body>
<div id="box"></div>
<script src="./Animate.js"></script>
<script>
window.onload = function(){
var animate = new Animate("#box", "width", 200, 1000, "linear")
animate.start()
}
</script>
</body>
</html>
这样利用策略模式我们就可以模拟一个类似css动画的方法库,在页面中可以看到一个方块匀速变大到200px的过程。我们想换一种运动方式只需要将linear
改为ease
。还有很多种实际应用,我们可以在实际开发中应用,例如表单校验,目前组件化流行,根据不同的数据展示不同的组件。都可以帮我们更好的优化代码。不用设计也可以实现所有功能,设计模式只是帮我们更好的使代码逻辑更清晰,更好维护代码,更好复用代码。