什么是设计模式:
设计模式的原则是“找出 程序中变化的地方,并将变化封装起来”,实现高效的可复用性,它的关键是意图,而不是结构。
js 常用的设计模式有 15 种:
一、单例模式:确保一个类只有一个实例对象,并暴漏给全局访问它。
二、策略模式:定义一系列的算法或者业务规则,把它们一个个封装起来,并且使它们可以相互替换
1、假设需要通过成绩等级来计算学生的最终得分,每个成绩等级有对应的加权值。我们可以利用对象字面量的形式直接定义这个组策略
// 定义成绩等级的加权值对象字面量
var levelMap = {
S: 10,
A: 8,
B: 6,
C: 4
};
// 定义最终得分的组合方法对象字面量(把一组具有相同功能的方法放到一个‘容器里’)
var scoreLevel = {
basicScore: 80,
S: function() {
return this.basicScore + levelMap['S'];
},
A: function() {
return this.basicScore + levelMap['A'];
},
B: function() {
return this.basicScore + levelMap['B'];
},
C: function() {
return this.basicScore + levelMap['C'];
}
}
// 定义调用方法
function getScore(level) {
return scoreLevel[level] ? scoreLevel[level]() : 0; //有定义的方法就执行 没有就返回 0
}
console.log(
getScore('S'),
getScore('A'),
getScore('B'),
getScore('C'),
getScore('D')
); // 90 88 86 84 0
2、在组合业务规则方面,比较经典的是表单的验证方法的封装。这里列出比较关键的部分
// 错误提示
var errorMsgs = {
default: '输入数据格式不正确',
minLength: '输入数据长度不足',
isNumber: '请输入数字',
required: '内容不为空'
};
// 规则集
var rules = {
minLength: function(value, length, errorMsg) {
if (value.length < length) {
return errorMsg || errorMsgs['minLength']
}
},
isNumber: function(value, errorMsg) {
if (!/\d+/.test(value)) {
return errorMsg || errorMsgs['isNumber'];
}
},
required: function(value, errorMsg) {
if (value === '') {
return errorMsg || errorMsgs['required'];
}
}
};
// 定义校验器类
function Validator() {
this.items = [];
};
// 定义校验器类的原型方法
Validator.prototype = {
constructor: Validator,
// 添加校验规则
add: function(value, rule, errorMsg) {
var arg = [value];
if (rule.indexOf('minLength') !== -1) {
var temp = rule.split(':');
arg.push(temp[1]);
rule = temp[0];
}
arg.push(errorMsg);
this.items.push(function() {
// 进行校验
return rules[rule].apply(this, arg);
});
},
// 开始校验
start: function() {
for (var i = 0; i < this.items.length; ++i) {
var ret = this.items[i]();
if (ret) {
console.log(ret);
// return ret;
}
}
}
};
// 测试数据
function testTel(val) {
return val;
}
// 定义校验器类的实例方法
var validate = new Validator();
validate.add(testTel('ccc'), 'isNumber', '只能为数字'); // 只能为数字
validate.add(testTel(''), 'required'); // 内容不为空
validate.add(testTel('123'), 'minLength:5', '最少5位'); // 最少5位
validate.add(testTel('12345'), 'minLength:5', '最少5位');
var ret = validate.start();
console.log(ret);
三、代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问
当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象 来控制对这个对象的访问,客户实际上访问的是 替身对象。
替身对象对请求做出一些处理之后, 再把请求转交给本体对象
代理和本体的接口具有一致性,本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情
代理模式主要有三种:保护代理、虚拟代理、缓存代理
1、保护代理
保护代理主要实现了访问主体的限制行为,以过滤字符作为简单的例子
// 主体,发送消息
function sendMsg(msg) {
console.log(msg);
}
// 代理,对消息进行过滤
function proxySendMsg(msg) {
// 无消息则直接返回
if (typeof msg === 'undefined') {
console.log('deny');
return;
}
// 有消息则进行过滤
msg = ('' + msg).replace(/泥\s*煤/g, '');
sendMsg(msg);
}
sendMsg('泥煤呀泥 煤呀'); // 泥煤呀泥 煤呀
proxySendMsg('泥煤呀泥 煤'); // 呀
proxySendMsg(); // deny
注:它的意图很明显,在访问主体之前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体,这属于保护代理的形式。
有消息的时候对敏感字符进行了过滤,这属于虚拟代理的模式。
2、虚拟代理
虚拟代理在控制对主体的访问时,加入了一些额外的操作。比如,在滚动事件触发的时候,也许不需要频繁触发,我们可以引入函数节流,这是一种虚拟代理的实现
// 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理
function debounce(fn, delay) {
delay = delay || 200;
var timer = null;
return function() {
var arg = arguments;
// 每次操作时,清除上次的定时器
clearTimeout(timer);
timer = null;
// 定义新的定时器,一段时间后进行操作
timer = setTimeout(function() {
fn.apply(this, arg);
}, delay);
}
};
var count = 0;
// 主体
function scrollHandle(e) {
console.log(e.type, ++count); // scroll
}
// 代理
var proxyScrollHandle = (function() {
return debounce(scrollHandle, 500);
})();
window.onscroll = proxyScrollHandle;
3、缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的缓存,提升效率。来个栗子,缓存加法操作
// 主体
function add() {
//能将具有length属性的对象转成数组
var arg = [].slice.call(arguments);
return arg.reduce(function(a, b) {
return a + b;
});
}
// 代理
var proxyAdd = (function() {
var cache = [];
return function() {
var arg = [].slice.call(arguments).join(',');
// 如果有,则直接从缓存返回
if (cache[arg]) {
return cache[arg];
} else {
var ret = add.apply(this, arguments);
return ret;
}
};
})();
console.log(
add(1, 2, 3, 4),
add(1, 2, 3, 4),
proxyAdd(10, 20, 30, 40),
proxyAdd(10, 20, 30, 40)
); // 10 10 100 100