javascript各种设计模式
- 设计模式之单例模式
- 设计模式之构造函数模式
- 设计模式之建造者模式
- 设计模式之工厂模式
- 设计模式之代理模式
- 设计模式之命令模式
设计模式之原型模式
看了这么多模式之后,你或许很茫然,但是在实际写代码的过程中,这些设计模式能起到不少作用,下面我们就来讨论每一种设计模式的具体实现以及他们的使用方法。
一、设计模式之单利模式
1、概念:
在传统的开发工程师眼里,单利模式就是保证每一个类只有一个实例,我们在实现时首先判断实例是否存在,如果存在,则直接返回,如果不存在就创建了再返回,这就确保了每一个类只有一个实例对象。在javascript里单例作为一个命名空间的提供者,从全局里提供一个唯一的访问点来访问对象。
2、作用以及注意事项:
作用:1.模块间通信
2.系统中某个类的对象只能存在一个
3.保护自己的属性和方法
注意事项:1.注意this的使用
2.闭包容易造成内存泄漏
3.使用继承时注意new的使用。
3、实现
最简单的一种办法就是使用是对象字面量的方法,其字面量里可以包含大量的属性和方法。
var firstObject = {
property1: "something",
property2: "something else",
method1: function () {
console.log('hello web!');
}
};
如果你要扩展该对象,我们可以提供自己的私有成员,然后我们通过闭包在其内部封装这些变量和函数声明。我们可以实现私有或者公有的方法。我们再看下面这一段代码:
var firstObject= function () {
/* 这里声明私有变量和方法 */
var privateVariable = 'something private';
function showPrivate() {
console.log(privateVariable);
}
/* 公有变量和方法(可以访问私有变量和方法) */
return {
publicMethod: function () {
showPrivate();
},
publicVar: 'the public can see this!'
};
};
var single = firstObject();
single.publicMethod(); // 输出 'something private'
console.log(single.publicVar); // 输出 'the public can see this!'
如果我们想做到只有在使用的时候才初始化,那该如何做呢?为了节约资源的目的,我们可以另外一个构造函数里来初始化这些代码,如下:
var firstjObiect= (function () {
var instantiated;
function init() {
/*这里定义单例代码*/
return {
publicMethod: function () {
console.log('hello world');
},
publicProperty: 'test'
};
}
return {
getInstance: function () {
if (!instantiated) {
instantiated = init();
}
return instantiated;
}
};
})();
/*调用公有的方法来获取实例:*/
firstjObiect.getInstance().publicMethod();
我们来看一下他的使用场景,单例一般是用在系统间各种模式的通信协调上
var firstObjectTester = (function () {
//参数:传递给单例的一个参数集合
function Singleton(args) {
//设置args变量为接收的参数或者为空(如果没有提供的话)
var args = args || {};
//设置name参数
this.name = 'SingletonTester';
//设置pointX的值
this.pointX = args.pointX || 6; //从接收的参数里获取,或者设置为默认值
//设置pointY的值
this.pointY = args.pointY || 10;
}
//实例容器
var instance;
var _static = {
name: 'SingletonTester',
//获取实例的方法
//返回Singleton的实例
getInstance: function (args) {
if (instance === undefined) {
instance = new Singleton(args);
}
return instance;
}
};
return _static;
})();
var singletonTest = firstObjectTester .getInstance({ pointX: 5 });
console.log(singletonTest.pointX); // 输出 5
下面给大家链接一下单例模式实现的一个例子,这里把那链接给大家:
解决Textarea的数据存储时的Html转Txt和展示时Txt转Html
上面这种方式主要实现的方法,还有其他的一些实现方法(来自汤姆大叔的博客)
方法一、
function Universe() {
// 判断是否存在实例
if (typeof Universe.instance === 'object') {
return Universe.instance;
}
// 其它内容
this.start_time = 0;
this.bang = "Big";
// 缓存
Universe.instance = this;
// 隐式返回this
}
// 测试
var uni = new Universe();
var uni2 = new Universe();
console.log(uni === uni2); // true
方法二、
function Universe() {
// 缓存的实例
var instance = this;
// 其它内容
this.start_time = 0;
this.bang = "Big";
// 重写构造函数
Universe = function () {
return instance;
};
}
// 测试
var uni = new Universe();
var uni2 = new Universe();
uni.bang = "123";
console.log(uni === uni2); // true
console.log(uni2.bang); // 123
二、设计模式之构造函数模式
1.概念
构造函数用于创建特定类型的对象,我们在里面不仅声明了使用的对象,构造函数还可以接受参数以便于第一次创建的时候设置对象的成员值。你也可以声明自定义类型对象的属性和方法。
2.作用和注意事项
作用:
- 用于创建特定类型的对象。
- 第一次声明的时候给对象赋值
- 可以传参进去,自己声明构造函数,赋予属性和方法
注意事项:
- 注意new的使用
- 区分与单例的区别
3.基本用法
在JavaScript里,构造函数通常是认为用来实现实例的,JavaScript没有类的概念,但是有特殊的构造函数。我们通过使用new操作付,我们可以告诉javascript我们要创建的的一个新的对象并且新对象的成员都是构造函数里定义的。在构造函数里,this指向的是新创建的对象,基本语法如下:
function PersonBir(name, year, month) {
this.month = month;
this.year = year;
this.name = name;
this.output= function () {
return this.name + ":" + this.year +"年"+ this.month+"月";
};
}
var ming= new PersonBir("小明", 1998, 5);
var gang= new PersonBir("小刚", 2000, 4);
console.log(ming.output());
console.log(gang.output());
这就是最简单的构造函数的方法,但是我们有没有发现一个问题,我们在继承的时候是不是感觉太麻烦了,所以我们可以通过原型prototype,将ouput方法添加到创建的对象上,来看下面的这一段代码
function PersonBir(name, year, month) {
this.month = month;
this.year = year;
this.name = name;
}
PersonBir.prototype.output=function(){
return this.name + ":" + this.year +"年"+ this.month+"月";
}
var ming= new PersonBir("小明", 1998, 5);
var gang= new PersonBir("小刚", 2000, 4);
console.log(ming.output());
console.log(gang.output());
这样output单实例可以在PersonBir对象实例中共享使用,我们通过给构造函数命名时采用函数名大写来表示,以便于区分普通函数。
4.强制使用new操作符
对于构造函数new操作符的使用我们来看下面这些代码,我们通过判断this值的instanceof是是不是PersonBir达到强制使用new操作符,以达到目的。
function PersonBir(name, year, month) {
//核心代码,如果为假,就创建一个新的实例返回。
if(!(this instanceof PersonBir)){
return new PersonBir(name, year, month);
}
this.month = month;
this.year = year;
this.name = name;
}
PersonBir.prototype.output=function(){
return this.name + ":" + this.year +"年"+ this.month+"月";
}
var ming= new PersonBir("小明", 1998, 5);
var gang= new PersonBir("小刚", 2000, 4);
console.log(ming.output());
console.log(gang.output());
三、建造者模式
1.概念
建造者模式可以将一个复杂的对象的构建与其表示相分离,使同样的构建过程可以创建不同的表示。如果我们用了建造者模式,那么用户就需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。建造者模式实际就是一个指挥者,一个建造者,一个使用指挥者调用具体建造者工作得出结果的客户。主要用于“分步骤构建一个复杂的对象”
2.作用以及注意事项
模式作用:
- 分步创建一个复杂的对象
- 解耦封装过程和具体创建组件
- 无需关心组件如何组装
注意事项:
- 一定要一个稳定的算法进行支持(“分步骤”是一个稳定的算法)
- 加工工艺是暴露的
了解了基本原理之后,我们来看下面这个实例,然后你就会对建造这模式有更深的认识了。
3.实例
一个土豪需要建一个别墅,然后直接找包工头,包工头再找工人把别墅建好。这里土豪不用直接一个一个工人的去找。只需包工头知道土豪需求,然后去找工人,工人干活,土豪也不需要知道房子具体怎么建,最后能拿到房就可以了。
//1.产出东西是房子
//2.包工头调用工人进行开工而且他要很清楚工人们具体的某一个大项
//3.工人是盖房子的 工人可以建厨房、卧室、建客厅
//4.包工头只是一个接口而已 他不干活 他只对外说我能建房子
function House() {
this.kitchen = ""; this.bedroom = ""; this.livingroom = "";
};
function Contractor() {
this.construct = function(worker) {
worker.construct_kitchen(); worker.construct_bedroom(); worker.construct_livingroom();
}
};
function Worker() {
this.construct_kitchen =function() {
console.log("厨房建好了");
}
this.construct_bedroom = function() {
console.log("卧室建好了");
}
this.construct_livingroom = function() {
console.log("客厅建好了");
}
this.submit = function() {
var _house = new House();
_house.kitchen = "finished";
_house.bedroom = "finished";
_house.livingroom = "finished";
return _house;
}
};
var worker = new Worker();
var contractor = new Contractor();
contractor.construct(worker);
// 主人要房子 var myhouse = worker.submit(); console.log(myhouse);
四、工厂模式
1.概念
工厂模式定义了一个用于创建对象的接口,这个接口由子类决定实例化哪一个类,该模式是一个类的实例化延迟到了子类。而子类可以重写接口的方法以便创建的时候指定自己的对象类型(抽象工厂)
2.作用和注意事项
作用:
- 对象构建十分复杂。
- 需要依赖具体的环境创建不同的实例
- 处理大量的具有相同属性的小对象
注意事项:
不能滥用工厂,有时候仅仅是给代码增加复杂度
3.使用方法
我们通过一个例子来演示这个问题,就像我们这个工厂里要生产不同类型的产品一样,我们每个类型写在一个方法,这样我们在生产的时候直接调用这个办法就行了。 那请看这段代码:
var productManager = {};
productManager.createProductA = function () {
console.log('ProductA');
}
productManager.createProductB = function () {
console.log('ProductB');
}
productManager.factory = function (typeType) {
return new productManager[typeType];
}
productManager.factory("createProductA");
我们在详细一点,假如我们想要在网页中插入一些元素,而这些元素的类型不固定,可能是图片可能是链接,甚至可能是文本,根据工行模式的定义我们需要定义相应的子类
var page = page || {};
page.dom = page.dom || {};
//子函数1:处理文本
page.dom.Text = function () {
this.insert = function (where) {
var txt = document.createTextNode(this.url);
where.appendChild(txt);
};
};
//子函数2:处理链接
page.dom.Link = function () {
this.insert = function (where) {
var link = document.createElement('a');
link.href = this.url;
link.appendChild(document.createTextNode(this.url));
where.appendChild(link);
};
};
//子函数3:处理图片
page.dom.Image = function () {
this.insert = function (where) {
var im = document.createElement('img');
im.src = this.url;
where.appendChild(im);
};
};
那我们如何定义工厂模式呢?其实很简单
page.dom.factory = function (type) {
return new page.dom[type];
}
使用方式如下:
var o = page.dom.factory('Link');
o.url = 'http://www.cnblogs.com';
o.insert(document.body);
五、代理模式
1.概念
代理模式就是为一个对象提供一个代用品或者占位符,以便控制对它的访问。也就是为了保障当前对象的单一职责,而需要创建另一个对象来处理当前对象的一些逻辑以提高代码效率判定状态等,代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西,常见的代理有远程代理,虚拟代理,安全代理,智能指引,我主要介绍最常见的两种代理模式就是虚拟代理。
2.作用和注意事项
1.远程代理(一个空间将不同空间的的对象进行局部代理)
2.虚拟代理(需要创建开销很大的对象,如图片加载)
3.安全代理(控制真实对象的访问权限)
4.智能引导(调用对象代理处理另外的一些事情,如垃圾回收机制)
注意事项:
不能滥用代理,有时候仅仅会增加代码的复杂程度。
我们来看虚拟代理实现图片预加载
// 图片加载函数
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
// 引入代理对象
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
// 图片加载完成,正式加载图片
myImage.setSrc( this.src );
};
return {
setSrc: function(src){
// 图片未被载入时,加载一张提示图片
myImage.setSrc("file://c:/loading.png");
img.src = src;
}
}
})();
// 调用代理对象加载图片
proxyImage.setSrc( "http://images/qq.jpg");
另一个就是虚拟代理合并http请求
// 文件同步函数
var synchronousFile = function( id ){
console.log( "开始同步文件,id为:" + id );
}
// 使用代理合并请求
var proxySynchronousFile = (function(){
var cache = [], // 保存一段时间内需要同步的ID
timer; // 定时器指针
return function( id ){
cache[cache.length] = id;
if( timer ){
return;
}
timer = setTimeout( function(){
proxySynchronousFile( cache.join( "," ) ); // 2s 后向本体发送需要同步的ID集合
clearTimeout( timer ); // 清空定时器
timer = null;
cache = [];
},2000 );
}
})();
// 绑定点击事件
var checkbox = document.getElementsByTagName( "input" );
for(var i= 0, c; c = checkbox[i++]; ){
c.onclick = function(){
if( this.checked === true ){
// 使用代理进行文件同步
proxySynchronousFile( this.id );
}
}
}
六、命令模式
1.概念
命令模式用于将一个请求封装成一个对象,从而可以用不同的参数对客户进行参数化,该模式将函数的调用请求和操作封装成一个单一的对象,然后对这个对象进行单一的处理,简而言之分为三个对象:
1.发起者:发出调用命令即可,具体如何执行,谁来执行并不清楚。
2.接收者:有对应的接口处理不同的命令,至于命令是什么,谁发出的,这不重要。
3.命令对象:上面我们说过,我们将发起者和接受者分开了,而这需要这个桥梁链接起来,这就是命令对象,命令对象接受发送者的调用,=然后调用接受者的相应接口。
2.作用以及注意事项
作用:
1.将封装、请求、调用结合为一体。
2.提高程序模块化的灵活性。
注意事项:
不需要借口一致,直接调用函数即可,以免造成浪费。
3.实例
// 发送者
var setCommond = function(button, fn) {
button.onClick = function() {
fn()
}
};
// 执行命令者
var menu = {
reFresh: function() {
console.log("刷新");
},
add: function() {
console.log("增加");
},
delete: function() {
console.log("删除");
}
};
// 命令对象
var commondObj = function(reciver) {
return function() {
reciver.reFresh();
}
};
var commondObj1 = commondObj(menu);
setCommond(btn1, commondObj1);
发送者(setCommond):不关心给哪个button,以及绑定什么事件,只要通过参数传入就好。
命令对象(commondObj):只需要接收到接受者的参数,当发送者发出命令时,执行就好。
接受者(menu):不用关心在哪里被调用被谁调用,只需要按需执行就好了。
七、原型模式
1.概念
原型模式是指用原型实例指向创建对象的种类,并通过拷贝这些原型创建新的对象。对于原型模式,我们利用javascript原型继承特性去继承特性这样一种方式来创建,也就是创建一个对象作为另一个对象的prototype属性。
var vehiclePrototype = {
init: function (carModel) {
this.model = carModel;
},
getModel: function () {
console.log('车辆模具是:' + this.model);
}
};
function vehicle(model) {
function F() { };
F.prototype = vehiclePrototype;
var f = new F();
f.init(model);
return f;
}
var car = vehicle('福特Escort');
car.getModel();
//上面代码来自汤姆大叔的博客
对于原型模式我们在javascript中使用无处不在,往往我们将原型与我们其他设计模式结合起来使用,能达到更好的效果。
总结:
说了这么设计模式,你理解的有多少呢,使用设计模式是为了提高我们解决问题的效率,不同的设计模式也是根据不同的应用环境来制订的,而在大多数情况下,设计方式一般是结合起来使用的,往往能达到更好的效果,制定好的设计方案有利于我们解决问题,有利于代码的维护,在使用过程中千万不要为了实用设计模式而强行使用,这样往往会提高代码的复杂度。