设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。适用于所有面向对象的编程语言
设计原则 SOLID
单一职责原则(SRP)
一个对象/方法,只做一件事情。
SRP原则的优点是降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。 最明显的缺点是会增加编写代码的复杂度。当我们按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。
开闭原则(OCP)
面向扩展开放,面向修改关闭,可扩展。
里氏置换原则(LSP)
子类能覆盖父类,父类能出现的地方子类就能出现,js中使用较少(弱类型&继承使用较少)
接口分离原则(ISP)
把大的接口拆分成小的接口(功能单一)
依赖倒置原则(DIP)
面向接口编程,依赖于抽象而不依赖具体,使用方只关注接口而不关注具体类的实现
js 设计模式
单例模式
核心思想是确保一个类只对应一个实例,并且对外暴露一个全局访问点。
js是弱类型语言,也有自己的构造函数和相应的实例,单例模式下,需要确保多次调用构造函数返回的都是同一个实例,也就是在实例化的时候先检查实例是否存在,有则直接返回,无则实例化后再返回。
var Singleton = function(name) {
this.name = name;
};
Singleton.prototype.getName = function() {
return this.name;
};
// 获取实例对象
function getInstance(name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
// 测试单体模式的实例
var a = getInstance("aa");
var b = getInstance("bb");
单例模式只能实例化一次,所以上面的a和b都是同一个实例,
a.getName() //aa
b.getName() //aa
闭包实现单例模式
// 单体模式
var CreateDiv = function(html) {
this.html = html;
this.init();
}
CreateDiv.prototype.init = function(){
var div = document.createElement("div");
div.innerHTML = this.html;
document.body.appendChild(div);
};
// 代理实现单体模式
var ProxyMode = (function(){
var instance;
return function(html) {
if(!instance) {
instance = new CreateDiv("我来测试下");
}
return instance;
}
})();
var a = new ProxyMode("aaa");
var b = new ProxyMode("bbb");
console.log(a===b);// true
单例模式实现弹窗 例子
// 创建div
var createWindow = function(){
var div = document.createElement("div");
div.innerHTML = "我是弹窗内容";
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
// 创建iframe
var createIframe = function(){
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
return iframe;
};
// 获取实例的封装代码
var getInstance = function(fn) {
var result;
return function(){
return result || (result = fn.call(this,arguments));
}
};
// 测试创建div
var createSingleDiv = getInstance(createWindow);
document.getElementById("Id").onclick = function(){
var win = createSingleDiv();
win.style.display = "block";
};
// 测试创建iframe
var createSingleIframe = getInstance(createIframe);
document.getElementById("Id").onclick = function(){
var win = createSingleIframe();
win.src = "http://cnblogs.com";
};
在Vue实现单例模式
先写一个单文件弹窗组件--demo.js,由于时间关系就写一个基本的div作为弹窗,组件接受一个变量 isShow 控制弹窗是否显示。
<template>
<div id="#demo" class="demo" v-show="isShow">
登录弹窗demo
</div>
</template>
<script>
export default {
name: "demo",
props: {
isShow: {
type: Boolean,
default: false
}
}
};
</script>
然后,在全局引用并挂载到vue的原型链上,
Vue.extend() 创建一个根据模板生成子类的构造器,data必须是函数
import demo from "@/components/global/demo";
import Vue from "vue";
//根据写好的组件创建构造器
const Con = Vue.extend(demo);
// 创建 ins 实例
const ins = new Con();
class LoginModule {
show() {
//判断实例是否存在
if (ins.$el) {
//存在修改实例中的显示变量
ins.isShow = true;
} else {
//不存在,先手动挂载实例,再将实例追加到body里面
ins.$mount(document.createElement("div"));
document.body.appendChild(ins.$el);
}
}
hide() {
ins.isShow = false;
}
}
let install = {
//install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
install(Vue) {
window.$LoginModule = Vue.prototype.$LoginModule = new LoginModule();
}
};
//调用 install.install
Vue.use(install);
策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
策略模式指的是定义一系列的算法,把它们一个个封装起来,将不变的部分和变化的部分隔开,实际就是将算法的使用和实现分离出来;算法的使用方式是不变的,都是根据某个算法取得计算后的奖金数,而算法的实现是根据绩效对应不同的绩效规则;
一个基于策略模式的程序至少由2部分组成,第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context,该Context接收客户端的请求,随后把请求委托给某一个策略类。
//代码如下:
var obj = {
"A": function(salary) {
return salary * 4;
},
"B" : function(salary) {
return salary * 3;
},
"C" : function(salary) {
return salary * 2;
}
};
var calculateBouns =function(level,salary) {
return obj[level](salary);
};
console.log(calculateBouns('A',10000)); // 40000
优点:
- 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句。
- 策略模式提供了开放-封闭原则,使代码更容易理解和扩展。
- 策略模式中的代码可以复用。
用策略模式编写表单验证功能: 原本的验证代码
var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){
if(registerForm.userName.value === '') {
alert('用户名不能为空');
return;
}
if(registerForm.password.value.length < 6) {
alert("密码的长度不能小于6位");
return;
}
if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
alert("手机号码格式不正确");
return;
}
}
第一步 封装策略对象
var strategy = {
isNotEmpty: function(value,errorMsg) {
if(value === '') {
return errorMsg;
}
},
// 限制最小长度
minLength: function(value,length,errorMsg) {
if(value.length < length) {
return errorMsg;
}
},
// 手机号码格式
mobileFormat: function(value,errorMsg) {
if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
};
第二步 实现context类 接收用户请求并委托给策略对象
var Validator = function(){
this.cache = []; // 保存效验规则
};
Validator.prototype.add = function(dom,rules) {
var self = this;
for(var i = 0, rule; rule = rules[i++]; ){
(function(rule){
var strategyAry = rule.strategy.split(":");
var errorMsg = rule.errorMsg;
self.cache.push(function(){
var strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategys[strategy].apply(dom,strategyAry);
});
})(rule);
}
};
Validator.prototype.start = function(){
for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {
var msg = validatorFunc(); // 开始效验 并取得效验后的返回信息
if(msg) {
return msg;
}
}
};
第三步 调用代码
// 代码调用
var registerForm = document.getElementById("registerForm");
var validateFunc = function(){
var validator = new Validator(); // 创建一个Validator对象
/* 添加一些效验规则 */
validator.add(registerForm.userName,[
{strategy: 'isNotEmpty',errorMsg:'用户名不能为空'},
{strategy: 'minLength:6',errorMsg:'用户名长度不能小于6位'}
]);
validator.add(registerForm.password,[
{strategy: 'minLength:6',errorMsg:'密码长度不能小于6位'},
]);
validator.add(registerForm.phoneNumber,[
{strategy: 'mobileFormat',errorMsg:'手机号格式不正确'},
]);
var errorMsg = validator.start(); // 获得效验结果
return errorMsg; // 返回效验结果
};
// 点击确定提交
registerForm.onsubmit = function(){
var errorMsg = validateFunc();
if(errorMsg){
alert(errorMsg);
return false;
}
}
使用闭包隐藏算法,并开放一个拓展入口
const PriceCalculate = (function() {
/* 售价计算方式 */
const DiscountMap = {
minus100_30: function(price) { // 满100减30
return price - Math.floor(price / 100) * 30
},
minus200_80: function(price) { // 满200减80
return price - Math.floor(price / 200) * 80
},
percent80: function(price) { // 8折
return price * 0.8
}
}
return {
priceClac: function(discountType, price) {
return DiscountMap[discountType] && DiscountMap[discountType](price)
},
addStrategy: function(discountType, fn) { // 注册新计算方式
if (DiscountMap[discountType]) return
DiscountMap[discountType] = fn
}
}
})()
PriceCalculate.priceClac('minus100_30', 270) // 输出: 210
PriceCalculate.addStrategy('minus150_40', function(price) {
return price - Math.floor(price / 150) * 40
})
PriceCalculate.priceClac('minus150_40', 270)