JavaScript的几种常见的设计模式:
首先了解一下什么是设计模式:
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
JavaScript中几种常见的设计模式:
1.单例模式
2.观察者模式
3.原型模式
单例模式:
什么是单例模式:
单例模式之所以这么叫作单例模式,是因为它限制一个类只能有一个实例化对象。经典的实现方式是,创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用。
JavaScript中没有类的定义,单例模式的特点是”唯一“和”全局访问“,那么我们可以联想到JavaScript中的全局对象,利用ES6的let不允许重复声明的特性,刚好符合这两个特点;因此可以得出,全局对象是最简单的单例模式;
如何实现一个单例:
var mySingleton = (function () {
var instance;
function init() {
function privateMethod(){
console.log( “I am private” );
}
var privateVariable = “Im also private”;
var privateRandomNumber = Math.random();
return {
publicMethod: function () {
console.log( “The public can see me!” );
},
publicProperty: “I am also public”,
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
getInstance: function () {
if ( !instance ) {
instance = init();
}
return instance;
}
};
})();
var myBadSingleton = (function () {
var instance;
function init() {
var privateRandomNumber = Math.random();
return {
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
getInstance: function () {
instance = init();
return instance;
}
};
})();
var singleA = mySingleton.getInstance();
var singleB = mySingleton.getInstance();
console.log(singleA.getRandomNumber()=singleB.getRandomNumber() ); // true
var badSingleA = myBadSingleton.getInstance();
var badSingleB = myBadSingleton.getInstance();
console.log(badSingleA.getRandomNumber()! badSingleB.getRandomNumber() ); // true
可以看到输出均为true
在实践中,当一个对象需要和另外的对象进行跨系统协作的时候,单例模式很有用。下面是一个单例模式在这种情况下使用的例子:
var SingletonTester = (function () {
function Singleton( options ) {
options = options || {};
this.name = “SingletonTester”;
this.pointX = options.pointX || 6;
this.pointY = options.pointY || 10;
}
var instance;
var _static = {
name: “SingletonTester”,
getInstance: function( options ) {
if( instance === undefined ) {
instance = new Singleton( options );
}
return instance;
}
};
return _static;
})();
var singletonTest = SingletonTester.getInstance({
pointX: 5
});
console.log( singletonTest.pointX );
我们可以看到输出为5
尽管单例模式有着合理的使用需求,但是通常当我们发现自己需要在javascript使用它的时候,这是一种信号,表明我们可能需要去重新评估自己的设计。这通常表明系统中的模块要么紧耦合要么逻辑过于分散在代码库的多个部分。单例模式更难测试,因为可能有多种多样的问题出现,例如隐藏的依赖关系,很难去创建多个实例,很难清理依赖关系。
观察者模式 :
观察者模式是一个被称作被观察者的对象,维护一组被称为观察者的对象,这些对象依赖于被观察者,被观察者自动将自身的状态的任何变化通知给它们。
当一个被观察者需要将一些变化通知给观察者的时候,它将采用广播的方式,这条广播可能包含特定于这条通知的一些数据。
当特定的观察者不再需要接受来自于它所注册的被观察者的通知的时候,被观察者可以将其从所维护的组中删除。 在这里提及一下设计模式现有的定义很有必要。这个定义是与所使用的语言无关的。通过这个定义,最终我们可以更深层次地了解到设计模式如何使用以及其优势。
我们现在可以通过实现一个观察者模式来进一步扩展我们刚才所学到的东西。这个实现包含一下组件:
被观察者:维护一组观察者, 提供用于增加和移除观察者的方法。
观察者:提供一个更新接口,用于当被观察者状态变化时,得到通知。
具体的被观察者:状态变化时广播通知给观察者,保持具体的观察者的信息。
具体的观察者:保持一个指向具体被观察者的引用,实现一个更新接口,用于观察,以便保证自身状态总是和被观察者状态一致的。
用一个通俗的例子来描述它:我们来看看生活中的观察者模式:现在房价这么高,你肯定是想要早点买房,但你看好的楼盘还没开盘,因此你就将你的电话留给售楼小姐,一旦楼盘推出就让她打电话给你。主动权在售楼方,而你只需要提供一个联系方式就行了这样你就不用担心楼盘出来你不知道了,也不需要每天都打电话去问楼盘推出了没。
这就是一个简单的观察者模式的例子。
在JavaScript中观察者模式的实现主要用事件模型来实现:例如DOM事件
DOM事件
document.body.addEventListener(‘click’, function() {
console.log(‘hello world!’);
});
虽然,使用dom事件可以轻松解决我们开发中的一部分问题;但是还有一些问题需要我们使用自定义事件来完成。
我们以预定手机为例,参考dom事件的原理来实现观察者模式,用用户的电话号码作为类型,用户的定购信息用一个回调函数来表示。
基本概念定义如下:
商家: 发布者
客户: 订阅者
缓存列表:记录客户的电话,方便商家遍历发通知消息给客户
// 定义商家
var merchants = {};
// 定义预定列表
merchants.orderList = {};
// 将增加的预订者添加到预定客户列表中
merchants.listen = function(id, info) {
if(!this.orderList[id]) {
this.orderList[id] = [];
}
this.orderList[id].push(info);
console.log(‘预定成功’)
};
//发布消息
merchants.publish = function() {
var id = Array.prototype.shift.call(arguments);
var infos = this.orderList[id];
// 判断是否有预订信息
if(!infos || infos.length === 0) {
console.log(‘您还没有预订信息!’);
return false;
}
// 如果有预订信息,则循环打印
for (var i = 0, info; info = infos[i++]😉 {
console.log(‘尊敬的客户先生:’);
info.apply(this, arguments);
console.log(‘已经到货了’);
}
};
// 定义一个预订者customerA,并指定预定信息
var customerA = function() {
console.log(‘白色高配版手机一台’);
};
// customerA 预定手机,并留下预约电话
merchants.listen(‘15973146111’, customerA); // 预定成功
// 商家发布通知信息
merchants.publish(‘15973146111’);
运行结果如下:
一个观察者的模板如下:
var observer = (function() {
var orderList = {},
listen,
publish,
remove;
listen = function(id, fn) {
…
};
publish = function() {
...
};
remove = function(id, fn) {
...
};
return {
listen: listen,
publish: publish,
remove: remove
}
})();
观察者模式的优缺点:
优点:
观察者模式的优点非常明显:一是时间上的解耦,而是对象之间的解耦。既可用于异步编程中,也可以用帮助我们完成更松耦合的代码编写。但它仍然有所不足:
缺点:
创建订阅者本身要消耗一定的时间和内存当订阅一个消息时,也许此消息并没有发生,但这个订阅者会始终存在内存中。观察者模式弱化了对象之间的联系,这本是好事情,但如果过度使用,对象与对象之间的联系也会被隐藏的很深,会导致项目的难以跟踪维护和理解。
原型模式:
什么是原型模式:
我们能够将原型模式认作是基于原型的继承中,我们创建作为其它对象原型的对象.原型对象自身被当做构造器创建的每一个对象的蓝本高效的使用着.如果构造器函数使用的原型包含例如叫做name的属性,那么每一个通过同一个构造器创建的对象都将拥有这个相同的属性。使用原型模式的好处之一就是,我们在JavaScript提供的原生能力之上工作的,而不是JavaScript试图模仿的其它语言的特性.而对于其它的模式来说,情况并非如此。这一模式不仅仅是实现继承的一种简单方式,它顺便还能够带来一点性能上的提升:当定义对象的一个方法时,它们都是使用引用创建的(因此所有的子对象都指向同一个函数),而不是创建属于它们的单独的拷贝。
一个简单的原型模式实例如下:
var Person=(function(){
var personCount=0;
function myPerson(name){
this.name=name;
personCount++;
console.log(‘created new person,current person count:’,personCount);
}
//静态属性,外部可通过Person.country直接访问,实例不可访问
myPerson.country='china';
myPerson.prototype={
//值类型,实例可修改,修改只影响当前实例
age:20,
//引用类型,实例可修改,修改里面的属性会影响所有实例
address:{
province:'四川',
city:'成都'
},
sayHello:function(){
console.log('hi,my name is ',this.name,',i am ',this.age);
},
myAddress:function(){
console.log(this.address.province,this.address.city);
}
};
return myPerson;
}());
console.log(Person.country);
var person1=new Person(‘zhangsan’);
person1.sayHello();
var person2=new Person(‘lisi’);
person2.sayHello();
var person3=new Person(‘wangww’);
person3.sayHello();
//修改值类型原型属性
person1.age=18;
console.log(‘修改person1.age后:’);
person1.sayHello();
person2.sayHello();
person3.sayHello();
//1修改引用类型原型属性的属性
person1.myAddress();
person2.myAddress();
person3.myAddress();
person1.address.city=‘德阳’;
console.log(‘修改person1.address.city后:’);
person1.myAddress();
person2.myAddress();
person3.myAddress();
//2修改引用类型原型属性
person1.address={
province:‘四川’,
city:‘巴中’
};
console.log(‘修改person1.address后:’);
person1.myAddress();
person2.myAddress();
person3.myAddress();
//3再次修改引用类型原型属性的属性
person1.address.city=‘绵阳’;
console.log(‘修改person1.address.city后:’);
person1.myAddress();
person2.myAddress();
person3.myAddress();
//4再次修改引用类型原型属性的属性
person2.address.city=‘自贡’;
console.log(‘修改person2.address.city后:’);
person1.myAddress();
person2.myAddress();
person3.myAddress();
可以在运行看到上述输出,由此我们发现:原型模式,就是创建一个共享的原型,通过拷贝这个原型来创建新的类,用于创建重复的对象,带来性能上的提升,原型模式的应用给我们带来了极大的便利。
总结:
通过对JavaScript几种设计模式的学习,使得我对JavaScript这门语言又有了更深一步的了解,也让我了解了在JavaScript中几种最基本的设计模式,也让我在实践中感受到了设计模式的优越性,在某些实际应用中,设计模式的确能给我们带来极大的便利性,不过我们也不能盲目的使用设计模式,频繁使用的话也有可能会导致错误,出现一些意想不到的问题。