在js中,设计模式的实现与java,c++等语言有很大的不同。
1.单例模式
单例模式的定义:保证一个类只有一个实例,并提供一个访问它的全局访问点。
分析:因为js是一门无类的语言,所以不能生搬硬套传统的单例模式,我们只需定义一个唯一的对象。也因为没有类,单例模式在当前开发时普遍使用框架的情况下,并不常用。
应用场景:生成登录弹窗。
let Singleton = function(name) {
this.name = name;
this.instance = null
}
Singleton.prototype.getName = function() {
alter(this.name)
}
Singleton.getInstance = function(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance
}
let a = Singleton.getInstance('one');
let b = Singleton.getInstance('two');
a === b; //true
2.策略模式
策略模式的定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以互相转化。
分析:业务比较复杂时使用。
3.构造函数模式
ECMAScript中构造函数可以用来创建特定对象,类似于Array,Date等原生的js对象
function Student(name,age,classa){
this.name=name;
this.age=age;
this.classa=classa;
this.sayHello=function(){
console.log(this.name,this.age,this.classa);
}
}
var me=new Student("xiaoai",22,"大三");
console.log(me.classa);
me.sayHello();
console.log(me instanceof Student);//true
由代码可以看出,于工厂模式除了函数名不同以外,还要注意:构造函数名的首字母大写(不过好像没有严格规定)。构造函数也没有显示创建的对象,使用了this,直接把属性和方法赋值给了this对象。没有return语句,实例化的时候要使用new,而且它能够识别对象(这正是构造函数模式胜于工厂模式的地方)。
构造函数虽然好用,但也有很大的缺点,就是每次创建实例的时候都要重新创建一次方法,实际应用中,每次创建对象的时候属性值不同,而对象的方法却是相同的,所以创建两次完全相同的方法是没有必要的
4.原型模式
js规定每一个创建的函数都有prototype(原型)属性,这个属性是指针,指向一个对象,而这个对象的用途是包含由特定类型的所有实例所共享的属性和方法,使用原型对象就可以让所有实例对象均包含这些属性及方法。
function per(){}
per.prototype.name='xiaoai';
per.prototype.age=22;
per.prototype.course=['php','javascript','java','C#'];
per.prototype.say=function(){
console.log(this.name+this.age+this.course);
}
var per1=new per();
var per2=new per();
per1.name='katherine';
per1.course.push('Html5');
per1.say();
per2.say();
per2.course.pop();
关于原型模式的缺点,我想也是很明显的,它省略了构造函数传递初始化参数这一环节,结果所有实例均在默认情况下取得了相同的属性值,虽然你可以在后来做出更改,但一点都不方便,这样还不是最大的问题,原型模式最大的问题是在于共享的本性所导致的,由于共享,因此一个实例修改了引用,另一个也随之更改了属性。因此一般不会单独使用原型模式。
5.构造函数 + 原型模式
function per(name,age,course){
this.name=name;
this.age=age;
this.course=course;
}
per.prototype.say=function(){
console.log(this.name+this.age+this.course);
}
var per1=new per('katherine',22,['C#','java','PHP','Javascript']);
var per2=new per ('xiaoai',21,['oracle','mysql','nodejs','html5']);
per1.say();
per2.say();
per1.course.pop();
per1.say();
per2.say();
由代码可以看出混合模式的分工:构造函数用于定义实例的属性,而原型模式用于定义方法和一些共享的属性。每个实例都会有自己的属性,但同时又共享着方法,最大限度的节省了内存。另外这种模式还支持传递初始参数。使用最广泛。
6.观察者模式与订阅发布模式
观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。
发布/订阅模式(Pub/Sub)是一种消息模式,它有 两个参与者 : 发布者和订阅者 。发布者向 某个信道发布一条消息,订阅者绑定这个信道,当有消息发布至信道时就会 接收到一个通知。最重要的一点是, 发布者和订阅者是完全解耦的,彼此并不知晓对方 的存在。两者仅仅共享一个信道名称。
从定义上可以看出,发布订阅模式里双方是完全解耦的,而在观察者模式里,目标对象管理这观察者,双方是耦合的,这是最主要的区别,而在发布订阅模式中多了一个中间层信道。所以它们两者并不相同,订阅发布模式是对观察者模式的进一步解耦。vue双向绑定的实现就使用了订阅发布模式。
我们从简单的代码开始,以Dog对象的fire方法来呈现两者的不同:
观察者模式实现
function Dog(){
this.handles = {};//保存所有观察者方法的引用
}
Dog.prototype.addHandle = function(type,func){
if(!(this.handles[type] instanceof Array)){
this.handles[type] = [];
}
this.handles[type].push(func);
}
Dog.prototype.fire = function(type){
var typeFunc = this.handles[type];
var len = typeFunc.length;
for(var i = ;i < len; i++){
typeFunc[i](event);
}
}
Dog.prototype.removeHandle = function(type,func){
var typeFunc = this.handles[type];
if(!(typeFunc instanceof Array)){
return;
}
var len = typeFunc.length;
for(var i = ;i < len; i++){
if(typeFunc[i] == func){
typeFunc.splice(i,1);
}
}
}
发布订阅模式实现
function rockets(){//信道
this.subers = {};
}
rockets.prototype.addSub = function(type,func){
if(!(subers[type] instanceof Array)){
subers[type] = [];
}
subers[type].push(func);
}
rockets.prototype.removeSub = function(type,func){
var typeFunc = subers[type];
if(!(typeFunc instanceof Array)){
return;
}
var len = typeFunc.length;
for(var i = ;i < len; i++){
if(typeFunc[i] == func){
typeFunc.splice(i,1);
}
}
}
rockets.prototype.fire = function(type){
var typeFunc = subers[type];
var len = typeFunc.length;
for(var i = ;i < len; i++){
typeFunc[i](event);
}
}
function Dog(){
this.rockets = new rockets();
}
Dog.prototype.fire = function(type){
this.rockets.fire(type);
}
从中可以看出发布订阅模式相对观察者模式多了一个中间层从而把发布和订阅分开,如果从这点代码量看来好像发布订阅完全多此一举,但是在大型项目中的团队合作来说,完全解耦是十分有必要的。
7.策略模式
定义一系列方法,封装起来使他们可以相互替换。也就是将策略封装在策略类中,当发起请求时管理类将请求委托给对应策略类。概括而言,就是讲对应不同情况的策略(即不同的处理方法)专门分装在一个对象中,即策略对象里面包含所有的策略。事件请求不直接请求该策略对象,而是有个管理对象来处理请求转发给策略对象的不同接口,这里其实也可以认为涉及到代理模式的概念, 主要目的是为了避免太多的逻辑判断语句的出现,解耦业务逻辑和算法策略。
使用场景:适用于业务逻辑中重复比较多依赖参数或者标识来区分不同的处理的场景。例如react中一个新建某个订单的页面中,存在很多的渲染input等。
/**
2 * 针对不同情况的策略算法封装在策略类fucs中,
3 * 从调用事件中去除繁琐的if或者switch逻辑判断。达到解耦的目的
4 * 加入后面再增加‘select’的选项增加对应的方法即可
5 */
6 var funcs = {
7 text:function(){
8 console.log('this is method for text')
9 },
10 radio:function(){
11 console.log('this is method for radio')
12 },
13 checkbox:function(){
14 console.log('this is method for checkbox')
15 },
16 default:function(){
17 console.log('this is method for default')
18 }
19 }
20
21 var renderDom = function(type){
22 /**
23 * 只需要根据不同的入参,自行匹配策略类中的接口即可。
24 */
25 return (funcs[type] || funcs['default'])()
26 }
27 renderDom('checkbox')
8.代理模式
代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景。比如,明星都有经纪人作为代理。如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演出的细节和报酬都谈好之后,再把合同交给明星签。
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
下面用一个故事、一段代码简单演示代理模式
原本:追慕者小明-->送花-->心上人A
代理:追慕者小明-->送花-->跟A相熟的代理B-->心上人A
// 代理模式 好处:
// 1、保护代理:代理B可以帮A过滤掉一些请求
// 2、虚拟代理:把一些开销大的对象,延迟到真正需要它时才创建
class Flower {
constructor (name) {
this.name = name
}
}
// 追慕者小明
let xiaoming = {
name: '小明',
sendFlower (target) {
target.receiveFlower(this.name)
}
}
// 代理B
let B = {
receiveFlower (fromWho) {
// 当然要等A好心情时才送花,也在送花也才创建花
A.listenGoodMood(() => {
A.receiveFlower(new Flower(fromWho + '的花'))
})
}
}
// 心上人A
let A = {
receiveFlower (flower) {
console.log('收到花:' + flower.name)
},
listenGoodMood (fn) {
setTimeout(() => {fn()}, 1000)
}
}
xiaoming.sendFlower(B)
运行结果:收到花:小明的花