一、工厂模式

工厂模式:通过工厂函数获取想要的内容

1
2
3
4
5
6
7
8
function creatPerson(name) {
// 创造出想要的对象
var obj = {};
// 给对象添加必要的数据
obj.name = name;
// 把创建好的对象返回出去
return obj;
}

// 利:简化创造对象的操作,只需要调用函数就可以获取对象
// 弊:无法判断出对象的归属

1
2
3
var obj1 = creatPerson('宁哥');
console.log(obj1 instanceof creatPerson);
console.log(obj1 instanceof Object);

二、单例模式

单例模式:通过单例创建出来的对象,只初始化(实例化)一次,以后调用创建方法
获取到的对象永远是第一次调用所创建出来的对象

1
2
3
4
5
6
function CreatePerson(name){
this.name = name;
}
CreatePerson.prototype.sayHi = function(){
console.log("hello");
}

1、不使用闭包,在函数内部调用内部函数
这样的做法不合理,因为这样还是可以每次调用这个函数创建出好多不同的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var person = null;//把这个写成全局中,对代码结构不太好
function SingalPerson(){
var person = null;
function aaa(name){
if(!person){
person = new CreatePerson(name);
}
}
aaa(name)
console.log(person);
//这样子return出去后函数里面就没有了person了,又还需要创建
return person;
console.log(person);
}
var a = SingalPerson();
console.log(a);

2、用闭包的方式是person被return出去后不会被销毁,一直存在内存中
//单例创造对象的函数

1
2
3
4
5
6
7
8
9
function SingalPerson(){
var person = null;
return function(name){
if(!person){
person = new CreatePerson(name);
}
return person;
}
}

虽然可以只调用一步SingalPerson();就可以实现单例效果,但是如果两个
对象分别是在函数里面创建,就需要在各自的函数里调用两次SingalPerson;
当然也可以把调用SingalPerson()的表达式写在全局中,这样每次创建对象
都可以来调用存贮SingalPerson()结果的变量,但是这样就把单例操作分
成了两部分,对程序的逻辑和结构方面不太好

1
2
3
4
5
6
7
8
9
10
var a = SingalPerson();
var obj1 = a("haha");
console.log(obj1);
//这样就可以看到单例的效果了
var obj3 = a("lala");
console.log(obj3);
//如果执行两次SingalPerson()就会创建两个不同的对象
//var b = SingalPerson();
//var obj2 = b("lala");
//console.log(obj2);

3、使用闭包和立即执行函数实现
要把最外面的函数只执行一遍,就可以使用到自调用,这样就不用在外面再去定义
执行全局的函数,当程序执行到这行代码时就立刻执行这个函数,再用一个变量来
保存这个函数的结果,然后可以在外面就可以调用这个变量了,因为外面的那个函
数返回的就是我们不断执行的内部函数

1
2
3
4
5
6
7
8
9
10
11
12
13
var aa = (function SingalPerson(){
var person = null;
return function(name){
if(!person){
person = new CreatePerson(name);
}
return person;
}
})();
var obj1 = aa("haha");
console.log(obj1);
var obj2 = aa("lala");
console.log(obj2);

三、单例模式的实例

单例模式实现模态窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var creatDiv = (function(){
var div = null;
// 向外返回的函数是真正用来创建标签、拼接标签的操作
return function() {
// 判断标签是否存在,不存在就创建,存在就直接返回这个标签
if (!div) {
div = document.createElement('div');
div.setAttribute('style', 'width: 100px;
height:100px; border:1px solid black');
document.body.appendChild(div);
}
return div;
}
})();
// 页面加载完毕后添加点击事件
window.onload = function () {
document.getElementById('btn').onclick = function () {
var divElement = creatDiv();
divElement.style.display = 'block';
divElement.onclick = function() {
document.body.removeChild(divElement);
divElement.style.display = 'none';
}
}
}

四、代理模式

代理是一个对象,它可以用来控制对本体对象的访问,它与本体对象实现了同样的接口,代理对象会把所有的调用方法传递给本体对象的;代理模式最基本的形式是对访问进行控制,而本体对象则负责执行所分派的那个对象的函数或者类,简单的来讲本地对象注重的去执行页面上的代码,代理则控制本地对象何时被实例化,何时被使用;我们在上面的单体模式中使用过一些代理模式,就是使用代理模式实现单体模式的实例化,其他的事情就交给本体对象去处理;
代理两个优点:
1、代理对象可以代替本体被实例化,并使其可以被远程访问;
2、它还可以把本体实例化推迟到真正需要的时候;对于实例化比较费时的本体对象,或者因为尺寸比较大以至于不用时不适于保存在内存中的本体,我们可以推迟实例化该对象
第一种方案:不使用代理的预加载图片函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 不使用代理的预加载图片函数如下
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
var img = new Image();
img.onload = function(){
imgNode.src = this.src;
};
return {
setSrc: function(src) {
imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif";
img.src = src;
}
}
})();

// 调用方式
myImage.setSrc(“https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
第二种方案:使用代理模式来编写预加载图片的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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("http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif");
img.src = src;
}
}
})();

// 调用方式
ProxyImage.setSrc(“https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
第一种方案是使用一般的编码方式实现图片的预加载技术,首先创建imgNode元素,然后调用myImage.setSrc该方法的时候,先给图片一个预加载图片,当图片加载完的时候,再给img元素赋值,第二种方案是使用代理模式来实现的,myImage 函数只负责创建img元素,代理函数ProxyImage 负责给图片设置loading图片,当图片真正加载完后的话,调用myImage中的myImage.setSrc方法设置图片的路径;

他们之间的优缺点如下:

  1. 第一种方案一般的方法代码的耦合性太高,一个函数内负责做了几件事情,比如创建img元素,和实现给未加载图片完成之前设置loading加载状态等多项事情,未满足面向对象设计原则中单一职责原则;并且当某个时候不需要代理的时候,需要从myImage 函数内把代码删掉,这样代码耦合性太高。
  2. 第二种方案使用代理模式,其中myImage 函数只负责做一件事,创建img元素加入到页面中,其中的加载loading图片交给代理函数ProxyImage 去做,当图片加载成功后,代理函数ProxyImage 会通知及执行myImage 函数的方法,同时当以后不需要代理对象的话,我们直接可以调用本体对象的方法即可;

从上面代理模式我们可以看到,代理模式和本体对象中有相同的方法setSrc,这样设置的话有如下2个优点:

  1. 用户可以放心地请求代理,他们只关心是否能得到想要的结果。假如我门不需要代理对象的话,直接可以换成本体对象调用该方法即可。
  2. 在任何使用本体对象的地方都可以替换成使用代理。
    五、观察者模式:
    发布—订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。

发布订阅模式的优点:

  1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。

  2. 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变;
    发布订阅模式的缺点:
    创建订阅者需要消耗一定的时间和内存。
    虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。

    1. 首先要想好谁是发布者(比如上面的卖家)。
    2. 然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(买家收藏了卖
      家的店铺,卖家通过收藏了该店铺的一个列表名单)。
    3. 最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数。

    我们还可以在回调函数里面添加一点参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var saleHouse = {};//发布者
saleHouse.clientlist = [];//客户的列表
saleHouse.listen = function(client){//注册被通知的客户,放入列表中
// this.clientlist.push(client);//
console.log(this.clientlist)//[]就是上面的saleHouse.clientlist
saleHouse.clientlist.push(client)
}
saleHouse.trigger = function(){//发通知
for(var i = 0,s;s = this.clientlist[i++];){//跟我们平时写方法相同
s.apply(this,arguments);//调用函数,有参数的情况下用apply
}
}
saleHouse.listen(function(position,price){
console.log('地点:'+position+"价格:"+price)
})
saleHouse.trigger('前海湾','120000')

六、适配器模式

适配器模式:
将一个对象或者类的接口翻译成某个指定的系统可以使用的另外一个接口.

适配器基本上允许本来由于接口不兼容而不能一起正常工作的对象或者类能够在一
起工作.适配器将对它接口的调用翻译成对原始接口的调用,而实现这样功能的代
码通常是最简的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var MyRenderMap = function(service) {
if(service.show instanceof Function){
service.show();
}
}
// MyRenderMap(baiduMap)
// MyRenderMap(gooleMap)
//如果方法名不同的话就要用到适配器
var gooleMap = {
abc:function(){
console.log('谷歌为你服务!')
}
}
var gooleMapAdapter={
show:functioun(){
return gooleMap.abc()//可能有些适配器需要返回
}
}
MyRenderMap(gooleMapAdapter)

上面的适配器也是每个不同的对象都要一个适配器(面版模式)



Javascript      Javascript

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!