单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用:只存在一个的浮窗(登录浮窗)、只绑定一次的事件、创建唯一实例对象。
实现原理:用变量标志当前是否为某个类创建过对象,如果有,在下一次获取该类的实例时,返回之前创建的对象。
实现 :
把全局变量当做单例来使用,但是要减少全局变量的使用,相对降低全局变量带来的命名污染。
- 使用命名空间
- 对象字面量
const namespace = {
a: 1,
b: () => {
alert(2);
}
};
- 动态创建命名空间
let MyApp = {};
MyApp.namespace = name => {
const parts = name.split('.');
let current = MyApp;
for (let i in parts) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
};
MyApp.namespace('event');
MyApp.namespace('dom.style');
- 使用闭包封装私有变量
const user = (() => {
const _name = 'sven',
_age = 29;
return {
getUserInfo: () => {
return _name + '_' + _age;
}
};
})();
惰性单例:在需要的时候才创建对象实例。
//抽离管理单例的逻辑
const getSingle = fn => {
let result;
return () => {
// result在闭包中永远不会被销毁
return result || (result = fn.apply(this, arguments));
};
};
// 创建dom元素
const createLoginLayer = () => {
let div = document.createElement('div');
div.innerHTML = '登录浮窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
const createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById('loginBtn').onclick = () => {
// 点击时只需要去取已经创建过的实例
let loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
绑定事件只绑定一次。
const bindEvent = getSingle(() => {
document.getElementById('div1').onclick = () => {
alert('click');
};
return true;
});
const render = () => {
bindEvent();
};
render();
render();
render(); // 执行了三次,但是div只绑定了一个事件
策略模式
定义:定义一系列的算法,把他们一个个封装起来,并且使它们可以相互替换,目的是将算法的使用和算法的实现分离开。
实现原理:一个策略模式的程序至少需要两部分组成,一组策略类(封装具体算法,并负责具体的计算过程)和环境类 Context(接受客户发起请求,把请求委托给某一个策略类)。
优点:
- 利用组合、委托、多态等技术和思想,可以有效地避免多重条件选择语句。
- 提供了开放-封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于拓展。
- 算法可以复用在系统的其他地方,从而避免重复工作。
- 利用组合和委托让 Context 拥有执行算法的能力,也是继承的一种更轻便的替代方案。
实现:
计算奖金
const strategies = {
S: salary => {
return salary * 4;
},
A: salary => {
return salary * 3;
},
B: salary => {
return salary * 2;
}
};
const calculateBonus = (level, salary) => {
return strategies[level](salary);
};
策略模式重构表单校验
- 将校验逻辑封装成策略对象
const strategies = {
isNonEmpty: (value, errorMsg) => {
if (value === '') {
return errorMsg;
}
},
minLength: (value, length, errorMsg) => {
if (value.length < length) {
return errorMsg;
}
},
isMobile: (value, errorMsg) => {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
};
- 定义发送请求
const validateFunc = () => {
const validator = new Validator();
// 校验规则
validator.add([
{ dom: registerForm.userName, rules: [{ rule: 'isNonEmpty', errorMsg: '用户名不能为空' }] },
{ dom: registerForm.password, rules: [{ rule: 'isNonEmpty', errorMsg: '密码不能为空' }, { rule: 'minLength:6', errorMsg: '密码长度不能少于6位' }] },
{ dom: registerForm.phoneNumber, rules: [{ rule: 'isMobile', errorMsg: '手机号码格式不正确' }] }
]);
// 获得校验结果
const errorMsg = validator.start();
return errorMsg;
};
registerForm.onsubmit = () => {
// 如果errorMsg有确切的返回值,说明未通过校验
const errorMsg = validateFunc();
if (errorMsg) {
alert(errorMsg);
return false; // 阻止表单提交
}
};
- 实现 Vadidator 类(Context),负责接收用户的请求并委托给 strategy 对象
class Validator {
constructor() {
// 保存校验规则
this.cache = [];
}
add(ruleArr) {
ruleArr.forEach(item => {
const { dom, rules } = item;
rules.forEach(rules => {
const { rule, errorMsg } = rules;
let array = rule.split(':');
this.cache.push(() => {
//移除规则名称,剩一个限制值或空
const strategy = array.shift();
array.unshift(dom.value);
array.push(errorMsg);
// array中有domvalue,限制值或无,errormsg
return strategies[strategy].apply(dom, array);
});
});
});
}
start() {
for (let i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
const msg = validatorFunc(); //开始校验,并获得返回信息
if (msg) {
return msg;
}
}
}
}
代理模式
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。
分类:
- 虚拟代理(把一些开销很大的对象,延迟到真正需要它的时候才去创建)
- 保护代理(用于控制不同权限的对象对目标对象的访问)
- 缓存代理(为一些开销大的运算结果提供暂时的存储,下次运算时如果参数一致,则可以直接返回之前存储的运算结果)
实现:
虚拟代理实现图片预加载
如果不用代理,预加载的实现:
const myImage = (() => {
const imgNode = document.createElement('img');
document.body.appendChild(imgNode);
let img = new Image();
img.onload = () => {
imgNode.src = img.src;
};
return {
setSrc: src => {
imgNode.src = 'loading.gif';
img.src = src;
}
};
myImage.setSrc('realImage.jpg');
})();
使用了虚拟代理:
const myImage = (() => {
const imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: src => {
imgNode.src = src;
}
};
})();
// 代理对象,在图片被真正加载好之前,出现占位图
const proxyImage = (() => {
return {
setSrc: src => {
myImage.setSrc('loading.gif');
img.src = src;
}
};
})();
// 调用
proxyImage.setSrc('realImage.jpg');
意义:
符合单一职责原则(就一个类而言,应该仅有一个引起它变化的原因),如果一个对象承担的职责过多,职责的耦合会导致脆弱和低内聚的设计,当发生变化时,设计可能会遭到意外的破坏。
符合开放-封闭原则,如果需求不再需要预加载,可以直接改变调用,而不是改动 myImage 对象,给 img 节点设置 src 和图片预加载两个功能被隔离在两个对象里,它们可以各自变化而不影响对方。
虚拟代理合并 HTTP 请求
通过代理函数收集一段时间内的请求,最后一次性发送给服务器。
const synchronousFile = id => {
// 发送请求
};
const proxySynchronousFile = (() => {
let cache = [], //数据暂存
timer;
return id => {
cache.push(id);
//确保不会覆盖已经启动的定时器
if (timer) {
return;
}
timer = setTimeout(() => {
synchronousFile(cache.join(',')); //两秒后发送请求
// 清空定时器和数据
clearTimeout(timer);
timer = null;
cache.length = 0;
}, 2000);
};
})();
// 调用代理函数
const checkbox = document.getElementsByTagName('input');
for (let i = 0, c; (c = checkbox[i++]); ) {
c.onclick = function() {
if (this.checked === true) {
proxySynchronousFile(this.id);
}
};
}
缓存代理做计算
// 因为箭头函数没有绑定arguments,所以还是用function
const mult = function() {
let a = 1;
for (let i = 0, l = arguments.length; i < l; i++) {
a *= arguments[i];
}
};
const plus = function() {
let a = 0;
for (let i = 0, l = arguments.length; i < l; i++) {
a += arguments[i];
}
};
// 创建缓存代理的工厂
const createProxyFactory = fn => {
let cache = {};
// 这里接收要计算的参数
return function() {
const args = Array.prototype.join.call(arguments, ',');
if (cache[args]) {
return cache[args];
}
return (cache[args] = fn.apply(this, arguments));
};
};
// 传入高阶函数
const proxyMult = createProxyFactory(mult);
const proxyPlus = createProxyFactory(plus);
proxyMult(1, 2, 3, 4);
proxyPlus(1, 2, 3, 4);