JS设计模式

JS设计模式总结

        • 创建型设计模式
          • 简单工厂模式
          • 工厂方法模式
          • 抽象工厂模式
          • 建造者模式
          • 原型模式
          • 单例模式
        • 结构型设计模式
          • 外观模式
          • 适配器模式
          • 代理模式
          • 装饰者模式
          • 桥接模式
          • 组合模式
          • 享元模式
        • 行为型设计模式
          • 模板方法模式
          • 观察者模式
          • 状态模式
          • 策略模式
          • 职责链模式
          • 命令模式(CM)
          • 访问者模式
          • 中介者模式
          • 备忘录模式
          • 迭代器模式
          • 解释器模式
        • 技巧型设计模式
          • 链模式
          • 委托模式
          • 数据访问对象模式(DAO)
          • 节流模式
          • 简单模板模式
          • 惰性模式
          • 参与者模式
          • 等待者模式
        • 架构型设计模式
          • SMD同步模块模式
          • AMD异步模块模式
          • Widget模式
          • MVC模式
          • MVP模式
          • MVVM模式

最近看了张容铭大佬的《JavaScript设计模式》,发觉自己以前写的代码好垃圾,遂总结一波书中内容,顺便备忘


创建型设计模式

处理对象创建的设计模式,通过某种方式控制对象的创建,来避免基本对象创建时,可能导致设计上的问题或增加设计


简单工厂模式
  • 概念:又叫静态工厂方法,用一个工厂对象创建一种产品对象类的实例
  • 用途:
    1. 对不同类进行实例化
    2. 创建相似类的实例对象
  • 例子:
//不同的类
var Phone = function(){
	this.price = 1000;
	...
};
Phone.protottype = { ... };
var Car = function(){
	this.name = 'WuLin';
	...
};
Car.prototype = { ... };
//创建简单工厂,用于将所有不同的基类集合到一个工厂对象中,调用时便只用记住工厂对象,而不用关心创建对象所依赖的基类
//返回类实例化对象
var GoodsFac = function(name){
	switch(name){
		case 'Phone':
			return new Phone();
		case 'Car':
			return new Car();
	}
}
//相似的类
//创建寄生对象,为对象添加方法和属性后,将对象返回
var FruitFac = function(name, number){
	var foo = new Object();
	foo.name = name;
	foo.number = number;
	return foo
}
  • 总结:通过对简单工厂来创建一些对象,可以让这些对象共有一些资源,而又私有一些资源。对简单工厂模式,使用场合通常限制在创建单一对象

工厂方法模式
  • 概念:通过对产品类的抽象,使其创建的业务主要负责用于创建多类产品的实例,本意是将产品类实例的创建,推迟到子类中
  • 用途:应对实例类不断变化的情况,且每个产品类都有各自的特性方法和属性
  • 例子:
var GoodsFac = function(goodsName, ...rest){
	//安全模式,防止调用工厂方法时,没有通过new调用
	//this指向构造函数时,说明不是通过函数名调用的函数
	if(this instanceof GoodsFac){
		//通过调用函数原型上的基类的构造方法,来实例化对象
		let foo = new this[goodsName](..rest);
		return foo
	}else{
		return new GoodsFac(goodsName, ...rest)
	}
};
//将不同产品类的构造函数放在工厂类的原型上,需求变化时就只用修改原型上的构造函数
GoodsFac.prototype = {
	Fruit: function(...rest){ ... },
	Car: function(...rest){ ... },
	...
}
  • 总结:通过工厂方法模式,可以轻松创建多个类的实例对象,这样工厂方法对象在创建对象的方式也避免了使用者与对象类之间的耦合,用户不必创建该对象的具体类,只需调用工厂方法即可

抽象工厂模式
  • 概念:通过对类的工厂抽象,使其业务负责对类簇的创建,而不负责某一类实例的创建
  • 用途: 可以为具有相同属性和方法,但是行为不同的实例类,提供父抽象类,父抽象类会定义子类簇的结构,
  • 例子:
//抽象工厂方法
var AdsFac = function(adsType, fatherType){
	if(AdsFac[fathertype]){
		//通过寄生继承,子类继承抽象父类的方法和属性
		function Fuc(){};
		Fuc.prototype = new AdsFac[fatherType]();	
		adsType.prototype = new Fuc();
		adsType.constructor = adsType;
	}else{
		throw new Error('未创建该抽象类');
	}
};
//抽象父类
AdsFac.PicAds = function(price, size){
	this.price = price;
	this.size = size;
};
AdsFac.PicAds.prototype = {
	setPrice: function(){
		throw new Error('抽象方法不能直接使用');
	},
	getPrice: function(){
		throw new Error('抽象方法不能直接使用');
	},
	...
};
//使用
var Cartoon = function(){ ... };
AdsFac(Cartoon, 'PicAds');
Cartoon.prototype.setPrice = function(){ ... };
  • 总结:抽象工厂是设计模式中最抽象的一种,也是创建模式中唯一一种抽象化创建模式。该模式创建的结果不是一个真实的对象实例,而是一个类簇,它制定了类的结构,这也就区分简单工厂模式创建单一对象,工厂方法模式创建多类对象。当然由于JS中不支持抽象化创建与虚拟方法,所以导致这种模式不能像其他面向对象语言中应用的那么广泛。

建造者模式
  • 概念:将一个复杂对象的构建层与其表示层相分离,同样的构建过程可以使用不同的表达方式
  • 用途:在创建对象实例时,有时会需要了解对象创建的过程,达到控制实例的行为的目的
  • 例子:
//不同部分的构造函数
var Person = function(name, sex){
	this.name = name;
	this.sex = sex;
};
Person.prototype = {
	getName: function(){ ... };
	...
};
var Work = function(skill){
	that.skill = skill;
	...
};
//建造模式构造函数
var Employee = function(name, sex, skill){
	let p = new Person(name, sex);
	p.skill = new Work(skill);
	return p;
};
//使用
var Jam = new Employee('Jam', 'mail', 'JS');
  • 总结:前集中结构设计模式,我们都只关心创建的结果,而不关心实例创建的过程。在建造者模式中我们关心的是实例创建的过程,因此将创建对象的类模块化,这样使被创建的类的每一个模块都可以得到灵活的运用与高质量的复用

原型模式
  • 概念:用原型实例指向创建对象的类,使用于创建新的对象的类的共享原型对象的属性以及方法
  • 用途:用于共享比较复杂的操作,因为过于常用,所以不被人们所注意
  • 例子:
var Ads = function(name, pic){
	this.name = name;
	this.pic = pic;
};
//在原型上定义比较复杂的操作
Ads.prototype.getMessage = function(){ ... };
//使用
var foo = new Ads('', '');
  • 总结:原型模式可以让多个对象共享同样的方法和属性。适合创建那些,需求一直变化,而导致对象结构不停改变的对象

单例模式
  • 概念:又被成为单体模式,是只允许实例化一次的对象类
  • 用途:
    1. 定义命名空间
    2. 管理模块
    3. 管理静态变量
  • 例子:
//定义命名空间
var Jam = {
	getDom: function(id){
		return document.getElementById(id);
	},
};
//管理静态变量
var foo = (function(){
	let conf = {
		MAX_VALUE = 10;	
	};
	return {
		getMaxValue: function(){
			return conf[MAX_AVLUE];
		}
	}
})();
  • 总结:JS单例模式经常作为命名空间对象使用,通过单例模式,我们可以将各个模块的代码井井有条的梳理在一起

结构型设计模式

关注于如何将类或对象的组合转化成更大、更复杂的结构,以简化设计


外观模式
  • 概念:为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统的访问更容易
  • 用途:将复杂的方法调用包装成简单的调用
  • 例子:
var foo = {
	dom: function(){ ... },
	style: {
		height: function(){ ... },
		...
	}
};
var bar = {
	addEventHandle: function(){ ... },
	...
};
//使用外观模式统一接口
var dom = {
	getDom: foo.dom,
	getHeight: foo.style.height,
	addEventHandle: bar.addEventHandle,
	...
};
  • 总结:当一个复杂的系统提供一系列复杂的接口方法时,为系统的管理方便会造成接口方法的使用极其复杂。通过外观模式,对接口的二次封装,隐藏其复杂性。因为只是对接口的二次包装,所以我们也无需了解接口方法的实现细节,只需了解接口调用规则即可

适配器模式
  • 概念:一个类(对象)的接口(方法、属性)转化为另一个接口,以满足用户需求,解决类(对象)的接口之间不适配的问题
  • 用途:
    1. 适配不同的框架
    2. 设置默认参数
    3. 服务器端数据适配
  • 例子:
//适配jq
window.A = A = JQuery;
//设置默认参数
function Foo(obj){
	this.name = obj.name || 'Jam';
	this.sex = obj.sex || 'male';
}
//服务端数据适配
function parseUrl(data){
	let datas = decodeURI(data);
	let parse = datas.slice(datas.indexOf('?') + 1).split('&');
	let query = [];
	parse.forEach(function(item){
		item = '"' + item.replace(/=/, '":');
		query.push(item);
	});
	query = '{' + query.join(',') + '}';
	return JSON.parse(query)
}
  • 总结:传统的设计模式中,适配器模式往往是适配两个类不兼容问题,而在JS中,适配器模式应用更广,比如适配两个代码库,适配前后端数据等。JS 中适配器模式更多的是应用在对象之间,为了使对象可用,我们一般会将对象拆封并重新包装,这样我们就要了解适配器的内部结构,这也是与外观模式的区别所在,当然适配器模式也同样解决了对象之间的耦合度。

代理模式
  • 概念:由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介作用
  • 用途:使用代理模板跨域
  • 例子:
//JSONP跨域
//请求方创建动态script标签,将要请求的数据作为回调函数的参数,将函数名放入url地址中的query字段
//服务器创建.js文件,调用query字段的函数,并将请求的数据作为函数参数
function jsonp(dom, dataName, yvName, funcName){
	let script = document.creatElement('script');
	script.url = yvName + '?' + dataName + '&' + funcName;
	dom.appendChild(script);
	return funcName
}
//使用代理模板跨域
//X域中的被代理页面
<script type="text/Javascript">
function callback(data){
	console.log('成功接收数据', data);
}
</script>
<iframe name="proxyIframe" id="proxyIframe" src="">
</iframe>
<form action="http://xxx.xxx.com/xxx.php" method="post" target="proxyIframe">
	<input type="text" name="callback" value="callback">
	<input type="text" name="proxy" value="http://xxx.xxx.com/proxy.html">
	<input type="submit" value="提交">
</form>
//X域中代理页面
//Y域将返回头设置为代理页面路径
<script>
window.onload = function(){
	if(top == self) return
	var arr = location.search.substr(1).split('&'),
		fn, args;
	for(var i = 0, len = arr.length, item; i < len; i++){
		item = arr[i].split('=');
		if(item[0] == 'callback')
			fn = item[1];
		else if(item[0] == 'arg')
			args = item[1]		
	}		
	try{
		eval('top.' + fn + '("' + args + '")');
	}catch(e){}
};
</script>
  • 总结:代理模式可以完全解决被代理对象与外界对象之间的耦合;同样,系统在加载比较大的资源或者实例,也可以使用代理模式延迟加载,以减少首屏加载的时间

装饰者模式
  • 概念:在不改变原对象的基础上,通过对其进行包装扩展(添加属性或方法),使其原有对象可以满足用户更复杂的需求
  • 用途:对原有对象进行扩展,而又不想去修改每处原代码的属性和方法
  • 例子:
//能够为原有对象添加新的方法的装饰函数
function decorator(oldObj, newBehavior){
	if(oldObj.newBehavior){
		let oldBehavior = oldObj.newBehavior;
		oldObj.newBehavior = function(...rest){
			oldBehavior.call(this, ...rest);
			newBehavior.call(this, ...rest);
		};
	}else{
		oldObj.newBehavior = newBehavior;
	}
}
  • 总结:装饰者模式可以在不了解原有功能的情况下,对功能进行扩展和增强。同样适配器模式也可以达到这个目的,但不同的是,适配器模式需要了解原有功能的具体实现,而装饰者模式是一种良性扩展,不需要了解其具体实现。

桥接模式
  • 概念:在系统沿着多个维度变化的同时,又不增加其复杂度并达到解耦
  • 用途:将不同对象间相同的部分抽象出来,通过一个匿名函数作为桥梁,将对象和方法解耦
  • 例子:
function Speed(v){
	this.speed = v;
}
Speed.prototype.run = function(){ ... }
function Color(rgb, opacity){
	this.rgb = rgb;
	this.opacity = opacity;
}
//使用桥接模式,构造对象
let Perosn = function(v, rgb, opacity){
	this.pace = new Speed(v);
	this.color = new Color(rgb, opacity);
}
  • 总结:桥接模式最主要的特点就是将实现层(如元素绑定事件),与抽象层(如修饰页面的UI逻辑)解耦分离,使两部分可以独立变化,由此可以看出桥接模式是对结构之间的解耦,而前面的抽象工厂模式与创建者模式,主要在于创建。

组合模式
  • 概念:又叫部分-整体模式,将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
  • 用途:创建表单
  • 例子:
//使用组合模式
//1.接口要统一,每个成员都有祖先
//2.组合要有容器类
var News = function(){
	this.children = [];
	this.element = null;
};
News.prototype = {
	init: function(){
		throw new Error('重写后才能使用');
	},
	add: function(){
		throw new Error('重写后才能使用');
	},
	getElement: function(){
		throw new Error('重写后才能使用');
	}
};
//容器类构造函数
var Container = function(id, parent){
	News.call(this);
	this.id = id;
	this.parent = parent;
	this.init();
};
Container.prototype = new News();
Container.prototype.init = function(){
	this.element = document.createElement('ul');
	this.element.id = this.id;
	this.element.className = 'new-container';
};
Container.prototype.add = function(child){
	this.children.push(child);
	this.element.appendChild(child.getElement());
	return this
};
Container.prototype.getElement = function(){
	return this.element
};
  • 总结:组合模式能够给我们一个清晰的组成结构。组合对象类通过继承同一个父类使其具有统一的方法,也方便我们统一管理与使用,当然此时单体成员与组合体成员的行为表现就比较一致了,这也就模糊了简单对象与组合对象之间的区别。

享元模式
  • 概念:运用共享技术有效的支持大量的细粒度的对象,避免对象间拥有相同的内容造成多余的开销
  • 用途:分页
  • 例子:
//每页显示5条消息
function Pagination(){
	let created = [];
	function createDiv(){
		let dom = document.createElement('div');	
		document.getElementById('content').appendChild(dom);
		created.push(dom);
		return dom
	}
	return{
		getDiv: function(){
			if(created.length < 5){
				return createDiv();
			}else{
				let div = created.shift();
				created.push(div);
				return div
			}
		}
	}
}
  • 总结:享元模式是为了提高程序的执行效率与系统的性能,因此在大型系统中应用的比较广泛。有时系统内存在大量对象,会造成大量内存占用,所以使用享元模式来减少内存消耗很有必要。

行为型设计模式

用于不同对象之间职责划分或算法抽象,行为模式不仅涉及类和对象,还涉及类或对象之间的交流模式并加以实现


模板方法模式
  • 概念:父类中定义一组操作算法骨架,而将一些实现步骤延迟到子类中,使得子类在不改变父类的算法结构的同时,可重新定义算法中某些实现步骤
  • 用途:创建整体结构相似,但是部分方法和属性不同的基类
  • 例子:
//模板父类
var Alert = function(data){
	if(!data) return;
	this.content = data.content;
	this.panel = document.creatElement('div');
	this.closeBth = document.creatElement('b');
	this.closeBth.className = 'alert';
	...
};
Alert.prototype = {
	init: function(){
		this.panel.appendChild(this.closeBth);
		...
		this.show();
	},
	bindEvert: {
		var me = this;
		this.closeBth.onclick = function(){
			me.fail();
			me.hide();
		};
		this.confirmBth.onclick = function(){
			me.success();
			me.show();
		};
	},
	fail: function(){ ... },
	...
};
//子类使用模板父类
var LeftBth = function(data){
	let Bth = function(data){
		Alert.call(this, data);
		//添加独有属性
		this.closeBth.calssName = 'left-alert';
	};
	Bth.prototype = new Alert();
	Bth.prototype.constructor = 'LeftBth';
	return new Bth;
};
  • 总结:模板方法模式的核心在于对方法的重用,它将核心方法封装在基类中,让子类继承基类的方法,实现方法的共享。

观察者模式
  • 概念:又称为发布-订阅模式或消息机制,定义了一种依赖关系,解决了主题对象与观察者之间功能的耦合
  • 用途:模块解耦
  • 例子:
//观察者对象
var Observer = (function(){
	let __message = [];
	return {
		//注册事件
		regist: function(type, fn){
			if(typeof __message[type] === 'undefine'){
				__message[type] = [];
				__message[type].push(fn);
			}else{
				__message[type].push(fn);
			}
		},
		//发布消息
		fire: function(type, args){
			if(typeof __message[type] !== 'undefine'){
				let events = {
					type: type,
					args: args || {}
				};
				for(let i of __message[type]){
					i.call(this, events);
				}
			}else{
				return
			}
		},
		//取消订阅
		remove: function(type, fn){
			if(typeof __message[type] !== 'undefine'){
				let i = 0,
					len = __message[type].length;
				for(; i < len; i++){
					__message[type][i] === fn && __message[type].splice(i, 1);
				}
			}
		}
	}
})();
//使用
let fn = function(){ ... };
let reg = Observer.regist('test', fn);
Observer.fire('test', { ... });
Observer.remove('test', fn);
  • 总结:观察者模式最主要的作用就是解决类或对象之间的耦合,解耦两个相互依赖的对象,使其依赖于观察者的消息机制。这样对于任意订阅者来说,其它订阅者的变化不会影响到自身,其自身可以是消息的执行者,也可以是消息的发送者,这都依赖于第三方观察者对象的方法中(订阅消息,注销消息、发送消息)的哪一种。团队开发中,很难有开发者能了解所以模块的具体细节,所以使用第三方观察者来传递模块之间的消息就很有必要。

状态模式
  • 概念:当一个对象内部状态发生了改变,会导致其行为的改变,这看起来像是改变了对象
  • 用途:减少多余的判断分支语句
  • 例子:
let character= (function(){
	//每一种状态是相互独立的,彼此之间无法替换
	let status = {
		move: function(){ ... },
		jump: function(){ ... },
		swim: function(){ ... }
	};
	let doSomething = function(behavior, args){
		if(!this.status[behavior]) return;
		this.status[behavior](args);
	};
	return { doSomething: doSomething }
})();
  • 总结:状态模式解决了程序中臃肿的分支判断语句的问题,将每个分支转化为一种状态独立出来,不至于执行时需要遍历所有分支。

策略模式
  • 概念:将定义的算法封装起来,使其相互之间可以替换,封装的算法具有一定独立性,不会随客户端的变化而变化
  • 用途:解决算法和使用者之间的耦合
  • 例子:
let priceReduce = function(price){
	//每一种情况就是一种策略
	let status = {
		500: function(){ return price * 0.9 },
		1000: function(){ return price * 0.85 },
		2000: function(){ return price * 0.8 }
	};
	if(status[price]) return;
	return status[price]()
};
  • 总结:策略模式的主要特色是创建一系列算法,每种算法处理的业务都是相同的,只是处理的过程或者结果不同,所以他们是可以相互替换的这样就解决了算法与使用者之间的耦合。在测试层面上说,策略模式每种算法都是独立的,有利于单元测试的执行,保证算法质量。工厂方法模式、状态模式和策略模式都优化了分支判断,其区别在于,工厂方法模式主要用于实例对象的创建,状态模式的每个状态都是独立存在,不能互补的,而策略模式的所有策略可以互相替换。

职责链模式
  • 概念:解决请求的发送者与请求的接受者之间的耦合,通过职责链上的多个对象分解请求流程,实现请求在多个对象之间的传递,直到最后一个对象完成请求的处理
  • 用途:将一个复杂的逻辑操作分解为几个步骤,然后顺序执行
  • 例子:
//发送请求,获取数据
let sendRequest = function(url){
	let xhr = new XMLHttpRequest();
	xhr.onStatuChange = function(){
		if(xhr.readyState == 4){
			if(xhr.status == 200){
				return xhr.responseText;
			}
		}
	};
	xhr.open('get', url);
	xhr.send(null);
};
//解析数据
let getData = function(data){
	if(!data) return;
	let news = JSON.parse(data);
};
  • 总结:职责链模式定义了请求传递的方向,通过多个对象对请求的传递,实现一个复杂的逻辑操作,方便对每一个颗粒化操作进行单元测试,并且还解决了请求的发送者与接收者之间的耦合。

命令模式(CM)
  • 概念:将请求与实现解耦并封装成独立对象,从而使不同的请求对客户端的实现参数化
  • 用途:可以自由的创建视图模块
  • 例子:
//创建一个可以添加任意数量模块的方法
let viewCommand = (function(){
	let creat = function(data){
		if(!data) return;
		for(let i of data){
			...
		}
	};
	let show = function(divName){ ... };
	return {
		creat: creat,
		show: show
	}
})();
//使用
viewCommand.creat([...]);
viewCommand.show('divName');
  • 总结:命令模式是将执行的命令封装,解决命令发起者与命令执行者之间的耦合。每一条命令实际上是一个操作。命令的发起者不必了解命令执行者的命令接口是如何调用的,所有的命令都被储存在命令对象中。命令模式的优点是解决命令使用者之间的耦合,新的命令很容易加入到命令系统中来。命令的使用具有一致性,多数的命令在一定程度上是简化操作方法的使用。

访问者模式
  • 概念:针对对象中结构的元素,定义在不改变对象的前提下访问结构中元素的新方法
  • 用途:不修改老代码的同时,添加新的功能
  • 例子:
let foo = function(){
	this.name = 'Jam';
};
let bar = function(){
	foo.call(this);
};
  • 总结:访问者模式是解决数据与数据操作方法之间的耦合,将数据的操作方法独立于数据,使其可以自由演变。因此访问者模式更适合于那种,数据稳定,但是数据的操作方法易变的环境下。对于同一个数据,可以被多个访问者使用,这大大提高了数据使用的灵活性。

中介者模式
  • 概念:通过中介者对象封装一系列对象之间的交互,使对象之间不再互相引用,降低他们之间的耦合度
  • 用途:类似于观察者模式,只是这里的消息系统变为中介者对象,且为单向通信
  • 例子:
var Mediator = (function(){
	let __message = [];
	return {
		//注册事件
		regist: function(type, fn){
			if(typeof __message[type] === 'undefine'){
				__message[type] = [];
				__message[type].push(fn);
			}else{
				__message[type].push(fn);
			}
		},
		//发布消息
		fire: function(type, args){
			if(typeof __message[type] !== 'undefine'){
				let events = {
					type: type,
					args: args || {}
				};
				for(let i of __message[type]){
					i.call(this, events);
				}
			}else{
				return
			}
		}
	}
})();
  • 总结:同观察者模式一样,中介者模式的主要任务也是通过模块间或对象间的复杂通信,来解决他们之间的耦合问题,中介者的本质是封装多个对象的交互,并且这些交互的实现是在中介者内部实现的。与外观模式的封装特性相比,中介者模式对多个对象交互式的封装,且这些对象一般处于同一层面,并且封装在中介者的内部,而外观模式的封装只是为了提供简单易用的接口,而不会添加其他新的功能。与观察者模式相比,观察者模式中的订阅者是双向的,而中介者模式中是单向的,只能接收消息,所有中介者被动的被中介者统一管理。

备忘录模式
  • 概念:在不破坏对象的封装性的前提下,在对象之外捕获并保存该对象的内部状态,以便日后对对象使用或者恢复到当前的某个状态
  • 用途:缓存数据,减少请求次数
  • 例子:
//使用闭包缓存数据
let foo = (function(){
	datas = [];
	setData = function(data){
		if(!data) return;
		datas.push(data);
	};
	useData = function(num){
		return datas[num]
	};
	return { 
		setData: setData,
		useData: useData
	}
})();
  • 总结:备忘录模式最主要的任务是对现有的数据或状态进行缓存,为将来某个时候使用或者恢复状态做准备。在JS中,备忘录模式主要用于对数据的缓存备份。缓存数据保存在备忘录对象中,使用数据必须通过对缓存对象接口的调用,所以备忘录对象也是对缓存数据的一次保护性封装。系统中缓存的数据越多时,占用的内存也越大,所以对缓存数据进行定时清理也很必要。

迭代器模式
  • 概念:在不暴露对象内部结构的同时,可以顺序的访问聚合对象内部的元素
  • 用途:
  • 例子:
//自定义each
let arrayEach = function(data, fn){
	for(let i = 0; i < data.length; i++){
		data[i] = fn.call(data[i], i, data[i]);
	}
};
  • 总结:通过迭代器我们可以顺序的访问一个聚合对象中的每一个元素,在开发中,迭代器模式大大简化了代码中的循环语句,使代码的结构紧凑,当然这些循环语句都转移到了迭代器内部,而我们使用的时候不用考虑其具体的实现细节和对象的结构,这样也就将使用者和对象内部结构之间解耦。

解释器模式
  • 概念:对于一种语言,给出文法表示形式,并定义一种解释器,通过这种解释器来解释语言中定义的句子
  • 用途:解析 url 路径
  • 例子:
//解析类似于 user="user"&name="Jam" 这样的字符串为JSON对象
function parseMsg(msg){
	msg = msg.replace(/\&/g, ',"').replace(/=/g, '":');
	return '{"' + msg + '}';
}
  • 总结:解释器即是对客户提出的要求,经过解析而形成的一个抽象解释程序。而是否可以应用解释器模式的一条重要准则,是能否根据需求,解析出一套完整的语法规。

技巧型设计模式

通过一些特定技巧来解决组件的某些方面问题,这类技巧一般通过实践经验总结


链模式
  • 概念:通过对对象方法中将当前对象返回,实现对同一个对象的多个方法的链式调用,从而简化对这个方法的多次调用
  • 用途:jq 的链式调用
  • 例子:
let foo = function(){
	this.name = 'jam';
};
foo.prototype = {
	setName: function(name){
		this.name = name;
		return this
	},
	...
};
  • 总结:JS中的链模式的核心思想就是,通过在对象中的每个方法调用执行完毕之后,返回当前对象 this。链模式可以使得代码紧凑而高效。

委托模式
  • 概念:多个对象接受并处理同一请求,它们将请求委托给另一个对象统一处理请求
  • 用途:在一个父节点下监听所有子节点的点击事件
  • 例子:
<ul id="foo">
	<li>one</li>
	<li>two</li>
	<li>three</li>
</ul>
<script>
	let ul = document.getElementById('foo');
	ul.addEventListener('click', function(){...});
</script>
  • 总结:委托模式是请求者将请求委托给被委托者去处理实现的,因此委托模式解决了请求与委托者之间的耦合。

数据访问对象模式(DAO)
  • 概念:抽象和封装对数据源的存储与访问,DAO 通过对数据源链接的管理,方便对数据的访问与存储
  • 用途:数据库 DAO 分层
  • 例子:
let LocalStorage = function(){};
LocalStorage.prototype = {
	status: {
		SUCCESS: 0,
		FAILURE: 1,
		OVERFLOW: 2,
		TIMEOUT: 3
	},
	storage: localStorage || window.localStorage,
	getKey: function(key){ ... },
	set: function(){ ... },
	get: function(){ ... },
	remove: function(){ ... }
};
  • 总结:DAO 主要用于对数据库操作的封装,使用户不必了解操作数据的具体细节,简化了用户的数据库操作。

节流模式
  • 概念:对复杂的业务逻辑进行节流控制,执行最后一次操作并取消其他操作,以提高性能
  • 用途:防止页面抖动,提高浏览器性能;图片延迟加载优化
  • 例子:
//监听鼠标移动事件
let foo = function(fn){
	let div = document.getElementById('foo'),
		timeHandle;
	div.addEventListener('mousemove', () => {
		if(timeHandle)
			clearTimeout(timeHandle);
		timeHandle = setTimeout(fn, 1000);
	}, false);
};
  • 总结:节流模式的核心思想是创建计时器,来延迟程序的执行,通过减少不必要的 dom 操作,来提高浏览器的性能。

简单模板模式
  • 概念:通过格式化字符串拼装出视图,避免创建视图时大量节点操作
  • 用途:提高页面性能
  • 例子:
let foo = function(id, calssName){
	return '
+ id + '" class="' + className + '">
'
};
  • 总结:简单模板模式意在解决使用 dom 操作创建视图时造成的资源消耗大、性能低下、操作复杂等问题。

惰性模式
  • 概念:减少每次代码执行时,重复性的分支判断操作,通过对对象的重新定义来屏蔽原对象中的分支判断
  • 用途:能力检测
  • 例子:
//提前执行
let foo = (function(dom, eventName, fn){
	if(documen.addEventListener){
		return function(dom, eventName, fn){
			dom.addEventListener(eventName, fn);
		}
	}else if(document.attachEvent){
		return function(dom, eventName, fn){
			dom.attachEvent(eventName, fn);
		}
	}else{
		return function(dom, eventName, fn){
			dom['on' + type] = fn;
		}
	}
})();
//惰性执行,在对象第一次调用时执行
let bar = function(dom, eventName, fn){
	if(documen.addEventListener){
		return function(dom, eventName, fn){
			dom.addEventListener(eventName, fn);
		}
	}else if(document.attachEvent){
		return function(dom, eventName, fn){
			dom.attachEvent(eventName, fn);
		}
	}else{
		return function(dom, eventName, fn){
			dom['on' + type] = fn;
		}
	}
};
bar(div, 'click', fn);
  • 总结:惰性模式是一种拖延模式,由于对象的创建或者数据的计算会花费高昂的代价,因此页面会延迟对这类对象的创建。

参与者模式
  • 概念:在特定的作用域中执行函数,并将参数原封不动的传递下去
  • 用途:给回调函数传入数据
  • 例子:
//bind
let foo = document.getElementById('foo');
let bar = function(args){ ... };
foo.addEventListener('click', bar.bind('something'));
//手动实现bind
function bind(context, args){
	return fn.apply(context, args);
}
  • 总结:参与者模式实际上是函数绑定和函数柯里化的结晶。

等待者模式
  • 概念:通过对多个异步进程监听,来触发未来的动作
  • 用途:promise
  • 例子:
class test{
	constructor(process){
	    this.statu = "pending";
	    this.msg = "";
	    process(this.resolve.bind(this), this.reject.bind(this));
	    return this
	}
	resolve(val){
	    this.statu = "fulfilled";
	    this.msg = val;
	}
	reject(error){
	    this.statu = "rejected";
	    this.msg = error;
	}
	then(resolve, reject){
	    switch (this.statu) {
	        case "fulfilled":
	            resolve(this.msg);
	            break;
	        case "pending":
	            reject(this.msg);
	            break;
	        default:
	            break;
   		}
   	}
}
  • 总结:等待者模式我们可以监听多个耗时的异步逻辑执行成功与否,并可执行相应的回调函数处理。

架构型设计模式

是一类框架结构设计,通过提供一些子系统,指定它们的职责,并将它们条理清晰的组织在一起


SMD同步模块模式
  • 概念:将复杂的系统分解成高内聚、低耦合的模块,使系统开发变得的可控、可维护、可扩展,提高模块的复用率;请求发出后,无论模块是否存在,都立刻执行后续的逻辑,实现模块开发中对模块的立即引用
  • 用途:解决多人开发时,排队式开发(一个人得等另一个人开发完之后,才能继续开发)
  • 例子:
//定义模块单体对象
let F = F || {};
//定义模板方法
F.define = function(str, fn){
	let parts = str.split('.'),
		old = parent = this,//F
		i = len = 0;
	if(parts[0] === 'F')
		parts = parts.sclice(1);
	if(parts[0] === 'define' && parts[0] === 'module')
		return;
	for(len = parts.length; i < len; i++){
		if(typeof parent[parts[i]] === 'undefined')
			parent[parts[i]] = {};
		old = parent;
		parent = parent[parts[i]];
	}
	if(fn)
		old[parent[--i]] = fn;
	return this;
};
//模块调用方法
F.module = function(){
	let args = [].slice.call(arguments),
		fn = args.pop(),
		parts = args[0] && args[0] instanceof Array ? args[0] : args,
		modules = [],
		modIDs = '',
		i = 0,
		iLen = parts.length,
		parent, j, jLen;
	while(i < jLen){
		if(typeof args[i] === 'string'){
			parent = this;
			modIDs = parts[i].replace(/^F\./,'').split('.');
			for(j = 0; j < modIDs.length; j++){
				parent = parent[modIDs[i]] ||false;	
			}
			modules.push(parent);
		}else{
			modules.push(parts[i]);	
		}
		i++;
	}
	fn.apply(null, modules);
};
//使用
F.define('dom', function(){ ... });//定义模块
F.module(['dom', document], function(){ ... });//调用模块
  • 总结:同步模块模式是模块化开发的最简单的一种形式,这种模式使得依赖的模板无论加载与否,都会立刻创建执行,这要求依赖的模块必须是创建过的。同步模块无法处理异步请求,所以在浏览器上的应用是比较有局限性的,但是在 node 上,文件是储存在本地的,所以更为适用。

AMD异步模块模式
  • 概念:请求发出后,继续其它业务逻辑,直到模块加载完成执行后续的逻辑,实现模块开发中对模块加载完成后的引用
  • 用途:异步加载文件中的模块
  • 例子:
//定义模块单体对象
let F = F || {};
//创建或调用模板方法
F.module = function(url, modDeps, modCallback){
	let args = [].slice.call(arguments),
		callback = args.pop(),
		deps = (args.length && args[args.length - 1] instanceof Array) ? args.pop() : [],
		url = args.length ? args.pop() : null,
		params = [],
		depsCount = 0,
		i = 0,
		len;
	if(len = deps.length){//if里面可以传入表达式
		while(i < len){
			(function(i){
				depsCount++;
				loadModule(deps[i], function(mod){
					params[i] = mod;
					depsCount--;
					if(depsCount === 0){
						setModule(url, params, callback);
					}	
				});
			})(i);
			i++;
		}
	}else{
		setModule(url, [], callback);	
	}
};
//定义模板缓存
let moduleCache = {};
//设置模块并执行模块构造函数
let setModule = function(moduleName, params, callback){
	let _module, fn;
	if(moduleCache[moduleName]){
		_module = moduleCache[moduleName];
		_module.status = 'loaded';
		_module.exports = callback ? callback.apply(_module, params) : null;
		while(fn = _module.onload.shift()){
			fn(_module.exports);
		}
	}else{
		callback && callback.apply(null, params);
	}
};
//异步加载依赖模块所在文件
let loadModule = function(moduleName, callback){
	let _module;
	if(moduleCache[moduleName]){
		_module = moduleCache[moduleName];
		if(_module.status === 'loaded'){
			setTiemout(callback(_module.exports), 0);
		}else{
			moduleCache[moduleName] = {
				moduleName: moduleName,
				status: 'loading',
				exports: null,
				onload: [callback]
			};
			loadScript(getUrl(moduleName));
		}
	}
};
//获取问价路径和加载脚本文件
let getUrl = function(moduleName){
	return String(moduleName).replace(/\.js$/g, '') + '.js';
};
let loadScript = function(src){
	let _script = document.creatElement('script');
	_script.type = 'text/JavaScript';
	_script.charset = 'UTF-8';
	_script.async = true;
	_script.src = src;
	document.getElementByTagName('Head')[0].appendChild(_script);
};
//使用
F.module('lib/dom', function(){ ... });
F.module('lib/event', ['lib/dom'], function(){ ... });
  • 总结:模块化开发不仅解决了系统的复杂性问题,而且减少了多人开发中变量、方法名被覆盖的问题。通过其强大的命名空间管理,使模块的结构更为合理。通过对模块的引用,提高了模块代码的复用率。异步模块模式在此基础上增加了模块依赖,使开发者不必担心某些方法尚未加载或未加载完全造成的无法使用的问题。异步加载部分功能也可将更多的首屏不必要的功能剥离出去,减少首屏加载成本。

Widget模式
  • 概念:(web widget 指的是一块可以在任意页面中执行的代码块)widget 模式是指借用 web widget 思想,将页面分解为部件,针对部件开发,最终组合成完整的页面
  • 用途:页面视图的开发
  • 例子:
//模板:{% text %}
//数据:{is_select: true, value: 'zh', text: 'zh-text'}
//输出结果:
F.module('lib/template', function(){
	//处理数据
	let _TplEngine = function(str, data){
		if(data instanceof Array){
			let html = '',
				i = 0,
				len = data.length;
			for(; i< len; i++){
				html += _getTpl(str)(data[i]);	
			}
			return hetml;
		}else{
			return _getTpl(str)(data);	
		}
	};
	//获取模板
	let _getTpl = function(str){
		let ele = document.getElementById(str);
		if(ele){
			let html = /^(textarea | input)$/i.test(ele.noedName) ? ele.value : ele.innerHTML;
			return _compileTpl(html);
		}else{
			return _compileTpl(str);
		}
	};
	//处理模板
	let _dealTpl = function(str){
		let _left = '{%',
			_right = '%}';
		return String(str)
			.replace(/</g, '<')
			.relpace(/>/g, '>')
			.replace(/[\r\t\n]/g, '')
			.replace(new RegExp(_left+'=(.*?)'+_right, 'g'), "',typeof($1)==='undefine'?'': $1,'")
			.replace(new RegExp(_left, 'g'), "');")
			.replace(new RegExp(_right, 'g'), "template_array.push('");
	};
	//编译执行
	let _comileTpl = function(str){
		let fnBody = `
			let template_array = [],
				fn = (function(data){
					let template_key = '';
					for(key in data){
						template_key += ('let ' + key + '= data[\"' + key + '\"];');
					}
					eval(template_key);
					template_array.push('"+_dealTpl(str)+"');
					template_key = null;
				})(templateData);
				fn = null;
				return template_array.join('');
		`;
		return new Function('templateData', fnBody);
	};
});
  • 总结:widget 架构模式是页面开发模块化,不仅仅是页面功能,甚至页面的每一个组件模块都可以独立的开发,这更适合多人的团队开发。并且降低相互之间的因功能或者视图创建的耦合影响概率。一个组件即是一个文件,也让我们更好的管理一个页面,当然组件的多样化也会组建一个更丰富的页面,同样也会让组件的复用率更高。

MVC模式
  • 概念:用一种将业务逻辑、数据、视图分离的方式组织架构代码
  • 用途:组件分层
  • 例子:
let MVC = {};
//模型层
MVC.model = function(){
	let M = {};
	M.data = {};
	M.conf = {};
	return {
		getDate: function(){ ... },
		getConf: function(){ ... },
		setData: function(){ ... },
		setConf: function(){ ... }
	}
}(); 
//视图层
MVC.view = function(){
	let M = MVC.model,
		V = {};
	return function(v){
		V[v]();
	}
}();
//控制层
MVC.control = function(){
	let M = MVC.model,
		V = MVC.view,
		C = {};
}();
  • 总结:MVC 架构模式很好的解决了页面中数据层、视图层、业务逻辑层之间的耦合关系,使他们得到显性的区分,使得层次间的耦合度降低。同时 MVC 模式也可以提高数据和视图的复用率。

MVP模式
  • 概念:view 层不直接引用 model 层内数据,而是通过 presenter 层实现对 model 层内数据的访问,即所以层次的交互都发生在 presenter 层中
  • 用途:解耦 view 层和 model 层
  • 例子:
let MVP = {};
//模型层
MVP.model = function(){
	let M = {};
	M.data = {};
	M.conf = {};
	return {
		getDate: function(){ ... },
		getConf: function(){ ... },
		setData: function(){ ... },
		setConf: function(){ ... }
	}
}(); 
//视图层
MVP.view = function(){
	let V = {};
	return function(src){
		//将字符串转化为HTML
		...
		return html
	}
}();
//控制层
MVP.presenter = function(){
	let M = MVC.model,
		V = MVC.view,
		C = {};
	return {
		init: function(){
			for(let i in C){
				C[i] && C[i](M, V, i);
			}
		}
	}
}();
  • 总结:MVP 与 MVC 模式最显著的区别就是将视图对数据的逻辑操作,放入 presenter 层中,降低了数据和视图之间的耦合,当然,同时也提高了数据和逻辑层之间的耦合度。通过对数据和视图的解耦,可以使数据和视图能够得到更加灵活的运用,提高了他们的复用率。

MVVM模式
  • 概念:为视图层量身定做一套视图模型,并在视图模型中创建属性和方法,为视图层绑定数据并实现交互
  • 用途:通过创建视图来控制管理器
  • 例子:
//视图层,带有绑定数据的html代码
<div class="first" data-bind="type: 'slider', data: demo1"></div>
<div class="second" data-bind="type: 'slider', data: demo2"></div>
<div class="third" data-bind="type: 'progressbar', data: demo3"></div>
<script>
let MVVM = {};
//创建模型层数据
MVVM.module = {
	demo1: { position: 60, totle: 200 },
	demo2: { ... },
	demo3: { ... }
};
//视图模型层
MVVM.VM = function(){
	//创建组件策略对象,保存实例化组件方法
	let Method = {
		progressbar: function(){
			let process = document.creatElement('div'),
				param = this.moudle;
			process.style.width = (param.position) + '%';
			...
		},
		slider: function(){ ... }
	};
	//获取视图层组件渲染数据的映射信息
	function getBindData(dom){
		let data = dom.getAttribute('bind-data');
		return !!data && (new Function('return ({' + data + ')}'))()
	}
	return function(){
		let doms = document.getElementByTagName('*'),
			ctx = null;
		for(let i = 0; i < doms.length; i++){
			ctx = getBindData(doms[i]);
			ctx.type && Method[ctx.type] && Method[ctx.type](doms[i], ctx);
		}
	}
}();
</script>
  • 总结:可以说 MVVM 模式是由 MVC 模式和 MVP 模式演变而来,不同的是 MVVM 模式可以使视图层更灵活,可以独立于数据模型层、视图模型层而独立修改、自由创建。

你可能感兴趣的:(JavaScript)