所有设计模式的主旨都是将变化和不变的部分隔离开来
单例模式
- 保证一个类仅有一个实例,并提供一个全局访问点,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等
const createSingleton = function(name, age){
if(!createSingleton.instance){
this.name =name;
this.age = age;
createSingleton.instance = this;
}
return createSingleton.instance
}
- 使用代理实现单例模式,把负责管理单例的逻辑移到了代理类 proxySingletonCreateDiv 中。这样一来, CreateDiv 就变成了一个普通的类,它跟 proxySingletonCreateDiv 组合起来可以达到单例模式的效果
var ProxySingletonCreateDiv = (function(){
var instance;
return function( html ){
if ( !instance ){
instance = new CreateDiv( html );
}
return instance;
}
})();
var a = new ProxySingletonCreateDiv( 'sven1' );
var b = new ProxySingletonCreateDiv( 'sven2' );
alert ( a === b )
- 惰性单例(触发条件时才创建单例),还是先将管理单例的逻辑抽离出来
var getSingle = function( fn ){
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
};
然后用getSingle包裹一下真正创建东西的函数,返回最终作为回调触发的函数
策略模式
统一制定一系列策略,通过控制传参触发不同策略,有效避免多重条件选择语句,将算法封装在独立的 strategies 中,使得它们易于扩展复用
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
}
};
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
}
用策略模式实现表单验证
- 先统一制定一系列验证策略
const strategies = {
isNonEmpty: function( value, errorMsg ){
if ( value === '' ){
return errorMsg;
}
},
minLength: function( dom, length, errorMsg ){
if ( dom.length < length ){
return errorMsg;
}
},
isMobile: function( value, errorMsg ){
if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
return errorMsg;
}
}
};
- 定义Validator类
const Validator = function(){
this.cache=[]
}
//rulesArr包含多个对象,每个对象代表一种限制策略,有strategy,errorMsg键值对
Validator.prototype.add = function(dom, rulesArr){
const self = this
for(let i=0;i<rulesArr.length;i++){
var rule = rulesArr[i];
var strategyAry = rule.strategy.split( ':' );
var errorMsg = rule.errorMsg;
//把每一个验证函数的触发函数存在cache中,在start方法里启动
self.cache.push(function(){
//取到策略的键名
var strategy = strategyAry.shift();
strategyAry.unshift( dom.value );
strategyAry.push( errorMsg );
return strategies[ strategy ].apply( dom, strategyAry );
}
}
//启动用户自定义的一系列表单验证
Validator.prototype.start = function(){
for ( let i = 0, i<this.cache.length;i++; ){
const validatorFunc = this.cache[i];
var errorMsg = validatorFunc();
if ( errorMsg ){
return errorMsg;
}
}
};
- 客户端调用方式
var validataFunc = function(){
var validator = new Validator();
validator.add( registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:6',
errorMsg: '用户名长度不能小于 10 位'
}]);
validator.add( registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密码长度不能小于 6 位'
}]);
validator.add( registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}]);
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function(){
var errorMsg = validataFunc();
if ( errorMsg ){
alert ( errorMsg );
return false;
}
}
发布订阅模式
class EventBus {
constructor() {
this.events = {}; // 存储事件和对应的订阅者回调
}
// 订阅
subscribe(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
return () => this.unsubscribe(event, callback); // 返回取消订阅函数
}
// 取消订阅
unsubscribe(event, callback) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
// 发布
publish(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(callback => callback(data));
}
}
// 导出一个全局单例
export const pubsub = new EventBus();
代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问
虚拟代理实现图片预加载
(预加载其实就是新建一个Image对象,附上src,然后onload...)
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
//在赋值src之前写好避免图片被cache住无法触发回调
imgNode.onload...
imgNode.onerror...
imgNode.src = src;
}
}
})();
var proxyImage = (function(){
//创建一个虚拟image对象,来代理对真正img节点的操作,通过 proxyImage 间接地访问 MyImage,执行放loading图,proxy onload后放真正图等操作
var img = new Image;
img.onload = function(){
myImage.setSrc( this.src );
}
return {
setSrc: function( src ){
myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
img.src = src;
}
}
})();
proxyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' )
虚拟代理合并网络请求
const syncFile = function(id){
console.log(‘开始同步文件’ + id)
}
const proxySyncFile = (function(){
var timer = null, cache=[];
return function(id){
cache.push(id)
if(timer){
return
}
// 代理函数收集2秒后调用真正的同步文件函数
timer = setTimeout(function(){
syncFile( cache.join( ',' ) );
clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000 );
}
})()
//使用
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySyncFile( this.id );
}
}};
缓存代理节省计算资源
var bigCalc = function(){
console.log( '开始昂贵运算' );
var a = 1;
for ( let i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
//缓存代理
const proxyCalc = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = bigCalc.apply( this, arguments );
}
})();
proxyCalc( 1, 2, 3, 4 ); // 输出: 24
proxyCalc( 1, 2, 3, 4 ); // 从缓存输出: 24
、、
工厂模式
/* 工厂类 */
class Factory {
static makeProduct(type) {
switch (type) {
case 'Product1':
return new Product1()
case 'Product2':
return new Product2()
default:
throw new Error('当前没有这个产品')
}
}
}
/* 产品类1 */
class Product1 {
constructor() { this.type = 'Product1' }
operate() { console.log(this.type) }
}
/* 产品类2 */
class Product2 {
constructor() { this.type = 'Product2' }
operate() { console.log(this.type) }
}
const prod1 = Factory.makeProduct('Product1')
prod1.operate() // 输出: Product1
const prod2 = Factory.makeProduct('Product3') // 输出: Error 当前没有这个产品
装饰者模式
用面向切面编程的范式(AOP)来完成装饰函数,赋予原函数新功能
Function.prototype.before = function( beforefn ){
var __self = this; // 保存原函数的引用
return function(){ // 返回包含了原函数和新函数的"代理"函数
beforefn.apply( this, arguments ); // 执行新函数,且保证 this 不被劫持,新函数接受的参数也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply( this, arguments ); // 执行原函数并返回原函数的执行结果,并且保证 this 不被劫持
}
}
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
};
用法:
Function.prototype.before = function( beforefn ){
var __self = this;
return function(){
beforefn.apply( this, arguments );
return __self.apply( this, arguments );
}
}
document.getElementById = document.getElementById
.before(function(){alert (1)})
.before(function(){alert(2)})
var button = document.getElementById( 'button' );
document的getElementById方法是before返回的新函数:
function(){
beforefn.apply( this, arguments );
return __self.apply( this, arguments );
}
注意:这里的this指向的是调用before的对象,也就是原getElementById函数
应用实例
数据统计上报
showLogin = showLogin.after( log )
表单上报
formSubmit = formSubmit.before( validate )
命令模式
var MoveCommand = function( receiver, pos ){
this.receiver = receiver;
this.pos = pos;
this.oldPos = null;
};
MoveCommand.prototype.execute = function(){
this.oldPos = this.receiver.dom.getBoundingClientRect()[ this.receiver.propertyName ];
// 记录小球开始移动前的位置
this.receiver.start( 'left', this.pos, 1000, 'strongEaseOut' );
};
MoveCommand.prototype.undo = function(){
this.receiver.start( 'left', this.oldPos, 1000, 'strongEaseOut' );
// 回到小球移动前记录的位置
};
var moveCommand;
moveBtn.onclick = function(){
var animate = new Animate( ball );
moveCommand = new MoveCommand( animate, pos.value );
moveCommand.execute();
};
cancelBtn.onclick = function(){
moveCommand.undo(); // 撤销命令
};
享元模式
var Upload = function( uploadType){
//上传类型属于内部状态,是每个文件上传单例不变的部分
this.uploadType = uploadType;
};
Upload.prototype.delFile = function( id ){
//给对应id的文件对象设置了外部状态后,可以获取到该文件的大小和dom节点等
uploadManager.setExternalState( id, this ); // (1)
if ( this.fileSize < 3000 ){
return this.dom.parentNode.removeChild( this.dom );
}
if ( window.confirm( '确定要删除该文件吗? ' + this.fileName ) ){
return this.dom.parentNode.removeChild( this.dom );
}
};
定义一个工厂来创建 upload 对象,如果某种内部状态对应的共享对象已经被创建过, 那么直接返回这个对象,否则创建一个新的对象
var UploadFactory = (function(){
var createdFlyWeightObjs = {};
return {
create: function( uploadType){
if ( createdFlyWeightObjs [ uploadType] ){
return createdFlyWeightObjs [ uploadType];
}
return createdFlyWeightObjs [ uploadType] = new Upload( uploadType);
}
}
})();
最后来书写uploadManager和启动上传函数:
var uploadManager = (function(){
var uploadDatabase = {};
return {
//manager的add方法 => 通过工厂create方法 => 调用构造函数创建或直接返回已存在文件上传单例 => 更新DOM => 将文件名,大小等信息写入database
add: function( id, uploadType, fileName, fileSize ){
var flyWeightObj = UploadFactory.create( uploadType );
var dom = document.createElement( 'div' );
dom.innerHTML =
'<span>文件名称:'+ fileName +', 文件大小: '+ fileSize +'</span>' +
'<button class="delFile">删除</button>';
dom.querySelector( '.delFile' ).onclick = function(){
flyWeightObj.delFile( id );
}
document.body.appendChild( dom );
//享元的灵魂,不直接赋给对象,因为所有文件共享一个上传对象,名称大小会被重复赋值覆盖,存在database中,用的时候先把外部状态set进去再取
uploadDatabase[ id ] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj ;
},
//manager的setExternalState方法 => 从database取出对应文件信息对象 => 把该对象所有属性赋给文件上传单例
setExternalState: function( id, flyWeightObj ){
var uploadData = uploadDatabase[ id ];
for ( var i in uploadData ){
flyWeightObj[ i ] = uploadData[ i ];
}
}
}
})();
var id = 0;
window.startUpload = function( uploadType, files ){
for ( var i = 0, file; file = files[ i++ ]; ){
var uploadObj = uploadManager.add( ++id, uploadType, file.fileName, file.fileSize );
}
};
现在就算现在同时上传 2000 个文件,需要创建的 upload 对象数量依然是 2。
startUpload( 'plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
{
fileName: '2.html',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
]);
对象池
对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接 new,而是转从对象池里获取。如 果对象池里没有空闲对象,则创建一个新的对象,当获取出的对象完成它的职责之后, 再进入池子等待被下次获取
对象池技术的应用非常广泛, HTTP 连接池和数据库连接池都是其代表应用。在 Web 前端开发中,对象池使用最多的场景大概就是跟 DOM 有关的操作。很多空间和时间都消耗在了 DOM节点上,如何避免频繁地创建和删除 DOM 节点就成了一个有意义的话题
职责链模式
将任务一步步在节点向后传递,直到有节点能执行它
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购,得到 100 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金预购,得到 50 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通购买,无优惠券' );
}else{
console.log( '手机库存不足' );
}
};
//定义Chain类
Class Chain {
constructor(fn){
this.fn = fn;
this.successor = null;
}
setSuccessor(fn){
this.successor = fn
}
start(){
const res = this.fn.apply(this, arguments);
if(res === 'nextSuccessor'){
return this.successor && this.successor.start.apply(this.successor,arguments);
}
return res;
}
//用来处理异步操作, 手动控制传给下一个节点的时机
next(){
return this.successor && this.successor.start.apply(this.successor,arguments);
}
}
用法
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );
然后指定节点在职责链中的顺序:
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
最后把请求传递给第一个节点:
chainOrder500.start( 1, true, 500 ); // 输出: 500 元定金预购,得到 100 优惠券
chainOrder500.start( 2, true, 500 ); // 输出: 200 元定金预购,得到 50 优惠券
chainOrder500.start( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.start( 1, false, 0 ); // 输出:手机库存不足
状态模式
var Light = function(){
this.currState = FSM.off; // 设置当前状态
this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement( 'button' ),
self = this;
button.innerHTML = '已关灯';
this.button = document.body.appendChild( button );
this.button.onclick = function(){
self.currState.buttonWasPressed.call( self ); // 把请求委托给 FSM 状态机
}
};
var FSM = {
off: {
buttonWasPressed: function(){
console.log( '关灯' );
this.button.innerHTML = '下一次按我是开灯';
this.currState = FSM.on;
}
},
on: {
buttonWasPressed: function(){
console.log( '开灯' );
this.button.innerHTML = '下一次按我是关灯';
this.currState = FSM.off;
}
}
}