资讯专栏INFORMATION COLUMN

javascript单例、代理、状态设计模式

0xE7A38A / 1546人阅读

摘要:代理模式代理模式为一个对象提供一个代用品或占位符,以便控制对于它访问。这种代理就叫虚拟代理。保护代理用于对象应该有不同访问权限情况。写时复制代理时虚拟代理的一种变体。

一、创建型设计模式(三大类设计模式)

创建型设计模式 --"创建"说明该类别里面的设计模式就是用来创建对象的,也就是在不同的场景下我们应该选用什么样的方式来创建对象。

1. 单例模式

==单例模式(Singleton)==:确保一个类只有一个实例,提供一个全局访问点。

==标准==单例实现
通常要实现一个单例模式并不复杂,无非就是用一个变量来标记当前是否已经为了某个类创建过对象了,如果创建过对象了,则在下一次获取该类的实例时,直接返回之前创建的对象。

/*单例实现代码如下:*/
var Singleton = function(name) {
  this.name = name;
  this.instance = null;
};
Singleton.prototype.getName = function() {
  alert(this.name);
};
Singleton.getInstance = function(name) {
  if (!this.instance) {
    this.instance = new Singleton(name);
  }
  return this.instance; 
};
//测试
var xiaoyi = Singleton.getInstance("小一");
var xiaoer = Singleton.getInstance("小二");
alert( xiaoyi === xiaoer ); //true 
/*另一种写法*/
var Singleton = function(name) {
  this.name = name;
};
Singleton.prototype.getName = function() {
  alert ( this.name );
};
Singleton.getInstance = (function(){
  var instance = null;
  return function(name) {
    if ( !instance )
      instance = new Singleton(name);
  }
  return instance;
})();
//测试
var xiaoyi = Singleton.getInstance("小一");
var xiaoer = Singleton.getInstance("小二");
alert( xiaoyi === xiaoer ); //true

结论: 无论我们创建的实例叫什么他总是同一个实例,这种写法实现的单例虽然较为简单,但是增加了类的“不透明性”,使用这必须知道这个是一个单例类。

透明单例

(试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,这个浮窗应该是唯一的,无论单机多少次按钮,每个浮窗只会被创建一次,那么这个浮窗就能用单例模式来创建)

现在我们来实现一个“透明”的单例类,这样用户使用这个类就能像使用其他类一样。我们将创建CreateDiv单例类,他的作用是负责在页面创建唯一的div节点。

/*使用透明的单例类来创建div*/
var CreateDiv = (function() {
  var instance;
  var CreateDiv = function( html ) {//传入内容
    if(instance){
      return instance;
    }
    this.html = html; //当前
    this.init();
    return instance = this;
  };
  CreateDiv.prototype.init = function() {
    var div = document.createElement("div");
    div.innnerHTML = this.html;
    document.body.appendChild(div);
  };
  return CreateDiv;
})();
//测试
var xiaoyi = new CreateDiv("小一");
var xiaoer = new CreateDiv("小二");
alert( xiaoyi === xiaoer ); //true

结论上边这个单例虽然“透明”了,但是为了把instance封装起来,我们使用了自执行匿名函数和闭包,并且让匿名函数返回真正的Singleton构造方法。增加了程序复杂度。
CreateDiv的构造函数实际上负责了两件事情。第一:创建对象,执行初始话方法,第二:保证只有一个实例对象。违背了单一原则。(单一原则:一个类最好只负责一项功能。控制类的粒度)

==导致的结果==当我们要使用这个类实例多个对象的时候,就必须要把控制创建唯一对象的那段去掉,这种修改会给我们带来不必要的烦恼。

javascript中的单例

关于前边所提到的几种单例模式的实现,更多是接近于传统面向对象语言中的实现,单例从类创建而来,在以类为中心的语言中,这是一种很自然的做法。比如java中如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来。

就javascript而言,它其实是一门无类(class-free)语言,在javascript中,既然我们只需要一个“唯一”的对象,为什么要先创建一个类?在javascript中经常把全局变量当成单例来使用。

var a = {};

用这种形式创建对象a,a是独一无二的。声明于全局下,提供全局访问点也是必然的。

这种做法缺陷:全局变量存在很多问题,容易造成命名空间污染。自己命名变量随时可能被别人覆盖掉了。【处理】作为开发这我们应该尽量减少全局变量的使用,即使使用也要把它的污染降到最低。

几种降低全局变量带来的命名污染。

使用命名空间

var namespace = {
a: {},
b: {},
};

使用闭包封装私有变量

var user = (function(){
  var _name = "sven",
  _age = 29;
  return {
    getUserInfo: function() {
      return _name + "-" _age;
    }
  }
})()

通用的惰性单例

var getSingle = function(fn) {
  var result;
  return function() {
    return result || (result = fn.apply(this, arguments));
  };
};
var createLoginLayer = function() {
  var div = document.createElement("div");
  div.innerHTML = "我是登录浮窗";
  div.style.display = "none";
  document.body.appendChild(div);
  return div;
};
var CreateSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById("loginBtn").onclick = function()  {
    var loginLayer = CreateSingleLoginLayer();
    loginLayer.style.display = "block";
};
var CreteSingleIframe = getSingle(function() { //动态加载第三页面
  var iframe = document.createElement("iframe");
  document.body.appendChild(iframe);
  return iframe;
});
document.getElementById("loginBtn").onclick = function()  {
  var loginLaryer = createSingleIframe();
  loginLayer.src = "http://baidu.com"
}

结论 以上我们把创建实例对象的职责和管理单例的职责分别放置在两个方法里,这两个方法可以独立变化而互不影响,而当他们连接在一起,就完成了创建唯一实例对象的功能。

总结当需求实例唯一、命名空间时,就可以使用单例模式。结合闭包特性,用途广泛。

二、结构型设计模式

结构型设计模式 --关注于如何将类或者对象组合成更大的结构,以便在使用的时候更简化。

1. 代理模式

==代理模式(proxy)==:为一个对象提供一个代用品或占位符,以便控制对于它访问。

代理模式在生活中的场景

比如:每个明星都有经纪人作为代理。如果想请明星来办一场商业演出,那么只能联系它的经纪人。经纪人会把商业演出的细节和报酬都谈好之后,再把合同交给明星签名确认。

graph LR
客户((圆))-->本体((圆))
graph LR
客户((圆))-->代理((圆))-->本体((圆))

代理相当于本体的替身,替身对请求做出一些处理后再把请求给本体对象。

故事场景

康康喜欢上玛丽亚,康康在二月十四想送一束花表白玛丽亚,刚好她两有共同的朋友michael,所以康康就叫micheal帮忙送花给玛丽亚。

/*用代码来写送花场景,首先是不通过(代理)*/
var Flower = function() {};
var kangkang = { //类
  sendFlower: function(target) { //传入送花目标
    var flower = new Flower(); //实例一朵花
    target.receiveFlower(flower); //花传给maria
  }
};
var maria = { //类
  receiveFlower: function(flower) { //接受类
    console.log("收到花了" + flower)
  }
};
kangkang.sendFlower(maria);
/*代理送花代码*/
var Flower = function() {};
var kangkang = {
  sendFlower: function(target){
    var flower = new Flower();
    target.receiveFlower(flower);
  }
};
var michael = {//michael为代理
  receiverFlower: function(flower) {
    maria.receiveFlower(flower);
  }
};
var maria = {
  receiveFlower: function(flower) {
    console.log("收到花了" + flower)
  }
};
kangkang.sendFlower(michael);

显然看起来这样送花不是有病? 能直接送到手为什么非要转别人的手送花呢。其实不然,当康康自己去送话,如果玛丽亚心情号成功几率有60%,但是心情差成功接近0,而通过jane的话jane可能较为了解玛丽亚,选择心情好的时候去送,这时候就事半功倍了。

/*心情好的时候送花*/
var Flower = function() {};
var kangkang = {
  sendFlower: function(target){
    var flower = new Flower();
    target.receiveFlower(flower);
  }
};
var jane = {//jane为代理
  receiverFlower: function(flower) {
    maria.listenGoodMood(function(){ //心情转好是后送花
      maria.receiveFlower(flower);
    })
  }
};
var maria = {
  receiveFlower: function(flower) {
    console.log("收到花了" + flower)
  },
  listenGoodMood: function(fn) {//一秒后心情转好
    setTimeout(function() {
     if(true){fn()} fn();
    }, 1000);
  }
};
kangkang.sendFlower(jane);

保护代理和虚拟代理

从上面的例子我们可以看出这两种代理的影子。代理jane可以帮助玛丽亚过滤掉一些请求,比如说送花的人不是康康,这样就直接可以在代理jane中被拒绝掉了。这种代理叫做保护代理。

假设new Flower一束花有期限,过期会凋谢,那么我们可以把new flower 交给代理jane去执行,代理jane会选择在玛丽亚心情好的时候再去买花而后送花。这种代理就叫虚拟代理。(虚拟代理是把一些开销很大的对象,延迟到真正需要它的时候再去创建。)

/*虚拟代理*/
var jane = {//jane为代理
  receiverFlower: function(flower) {
    maria.listenGoodMood(function(){ //心情转好是后送花
      var flower = new Flower(); //延迟创建 flower 对象
      maria.receiveFlower(flower);
    })
  }
};

虚拟代理实现图片的预加载

如果直接给某个img设置src属性的话,可能会由于网络太差,图片的位置往往有段时间会是一片空白,常见的作法就是用一张loading图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充把img节点中。这种场景就用虚拟代理。

/*创建一个图片元素并且提供一个设置s"r"c的接口*/
var myImage = (function() {
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
  setSrc: function(src) {
    imgNode.src = src;
  }
}
})();
/*引入代理ProxyImg对象,在图片真正被加载好之前用一张展位的菊花图来提示用户正在加载。*/
var proxyImage = (function(){
  var img = new Image;
  img.onload = function() { //当真正的图加载完了在把图片塞给他
    myImage.setSrc(this.src)
  }
  return {
    setSrc: function(src) { //一开始先设置一张菊花图给他
      myImage.setSrc("./loading.gif");
      img.src = src;
    }
  }
})();
proxyImage.setSrc("http://wangshangtupian.jpg"); //模拟的图片

代理的意义

单一职责原则就一个类而言,应仅有一个引起它变化的原因。

开放-封闭闭原则不用改变MyImage类或者添加接口,通过代理给系统添加了新的行为,这符合开闭原则。

结论当我们不需要预加载的功能,我们只需要直接请求本体,而不需要请求代理,这就使用代理的灵活之处。

虚拟代理合并HTTP请求

先想象一下这样一个场景:假设一个公司需要员工写周报,周报要交给总监批阅,总监手下有150个员工,如果我们每个人把周报发给总监,那总监可能就不用工作了,每周看周报。如果将周报发给组长,组长整理后再发给总监,那总监就轻松多了。

/*需要做一个文件同步的功能,当点击checkbox同时往另一台服务器同步文件,如果一秒点四个checkbox,那么网络开销会相当大。我们可以通过一个代理函数来收集一段时间内的请求,最后一次性发给服务器请求*/

1
2
3
4
5


//给checkout 绑定事件
var synchronousFile = function(id) {
  console.log("开始同步文件, id为:" + id);
};

var ProxySynchronousFile = (function(){
  var cache = [],//保存一段事件内需要同步的id
  timer; //定时器
  return function (id){
    cache.push(id);
    if(timer){ //保证不会覆盖已经启动的定时器
      return;
    }
    timer = setTimeout(function() {
      synchronousFile(cache.join(",")); //2秒后向服务器发送id的集合
      clearTimeout(timer);
      timer = null;
      cache.length = 0; //清空ID集合 
    }, 2000);
  }
})();

var checkbox = document.getElementsByTagName("input");
for (var i = 0, c; c= checkbox[i++];){
  c.onclick = function() {
    if(this.checked === true){
      ProxySynchronousFile(this.id);
    }
  }
}

缓存代理

缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接传出之前的存储的运算结果。

/*计算乘积*/
var mult = function() {
  console.log("开始计算乘积");
  var a = 1;
  for (var i = 0, l = arguments.length; i < l; i++){
    a = a * arguments[i];
  }
  return a;
}
var proxyMult = (function(){
  var cache = {};
  return function() {
    var args = Array.prototype.join.call(arguments, ",");
    if(args in cache){
      return cache[args];
    }
    return cache[args] = mult.apply(this, arguments);
  }
})();
proxyMult(1, 2, 3, 4); //24
proxyMult(1, 2, 3, 4); //24

总结代理模式应用广泛,比如说防火墙代理:控制网络资源的访问,保护主机不让坏人破坏。远程代理:为一个对象在不同的地址空间提供局部代表。保护代理:用于对象应该有不同访问权限情况。智能引用代理:取代了简单的指针,他在访问对象是执行一些附加操作,比如计算一个对象被应用的次数。写时复制代理:通常用于复制一个庞大的对象的情况。写时复制代理延迟了复制的过程。当对象真正被修改时,才对它进行复制操作。写时复制代理时虚拟代理的一种变体。更多就不再一一赘述了。

三、行为型设计模式

行为型设计模式 --不单单涉及到类和对象,更关注于类或者对象之间的通讯交流。

1. 状态模式

==状态模式(State Pattern)==:关键是区分事物内部的状态,事物内部的状态改变往往会带来事物行为的改变。

初识状态模式

想象一下:有一个电灯,只有一个控制电灯的开关。当电灯开着的时候按下开关,电灯会切换到关闭状态;再按一次开关,电灯将又被打开,同一个开关在不同的状态下表现出来的行为是不一样的。

var Light = function() {
  this.state = "off"; //电灯初始状态
  this.button = null; //电灯开关
}

Light.prototype.init = function() {
  var button = document.createElement("button").
  self = this;
  button.innerHTML = "开关";
  this.button = document.body.appendChild(button);
  this.button.onclick = function() {
    self.buttonWasPressed();//开关按下的行为函数
  }
};
Ligth.prototype.buttonWasPressed = function() {
  if (this.state === "off") {
    console.log("开灯");
    this.state = "on";
  } else if (this.state === "on") {
    console.log("关灯");
    this.state = "off";
  }
};
var light = new Light();
light.init();

令人遗憾的是这个世界上的电灯并不是只有这一种。某个酒店的灯,按一下开关弱光,在按一下开关强光,再按一下开光关闭。

Ligth.prototype.buttonWasPressed = function() {
  if (this.state === "off") {
    console.log("弱光");
    this.state = "ruoguang";
  } else if (this.state === "ruoguang") {
    console.log("强光");
    this.state = "qiangguang";
  } else if (this.state === "qiangguang"){
    console.log("关灯");
    this.state = "off";
  }
};

这个buttonWasPressed很明显违反了开放-闭合原则,每次新增或修改电灯的状态都需要从新改动buttonWasPressed方法中的代码,这让它成为一个非常不稳定的方法。

/*让我们改进一下*/
var  OffLightState = function(light){
  this.light = light;
}
OffLightState.prototype.buttonWasPressed = function() {
  console.log("弱光");
  this.light.setState(this.light.weakLightState);
}

var  WeakLightState = function(light){
  this.light = light;
}
WeakLightState.prototype.buttonWasPressed = function() {
  console.log("强光");
  this.light.setState(this.light.strongLightState);
}

var  strongLightState = function(light){
  this.light = light;
}
strongLightState.prototype.buttonWasPressed = function() {
  console.log("关灯");
  this.light.setState(this.light.OffLightState);
}
//改写类
var Light = function() {
  this.OffLightState = new OffLightState(this);
  this.WeakLightState = new WeakLightState(this);
  this.strongLightState = new strongLightState(this);
  this.button = null;
};

Light.prototype.init = function(){
  var button = document.createElement("button"),
  self = this;
  this.button = document.body.appendChild(button);
  this.button.innerHTML = "开关";
  this.currState = this.offLightState; //设置当前状态
  this.button.onclick = function(){
    self.currState.buttonWasPressed();
  }
}

Light.prototype.setState = function(newState){
  this.currState = newState;
};
var light = new Light();
light.init();

总结状态模式的优缺点

状态模式定义了状态与行为之间的关系,并将他们封装在一个类里,通过增加新的状态,很容易增加新的状态和转换。

避免Context无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了context原本过多的条件分支。

用对象代替字符串来记录当前的状态,使得状态的切换更加一目了然。

context中的请求动作和状态类中封装的行为可以非常容易的独立变化而不互相影响。

缺点:会在系统中定义许多的状态类,编写二十个状态类是一项枯燥乏味的工作,而且系统中会因此增加不少对象,另外由于逻辑分布在状态类中,虽然避开了不受欢迎的条件语句,但是也造成了逻辑分散的问题。我们无法在一个地方就看出整个状态机的逻辑。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100058.html

相关文章

  • JavaScript设计模式与开发实践》读书笔记

    摘要:订阅模式的一个典型的应用就是后面会写一篇相关的读书笔记。享元模式享元模式的核心思想是对象复用,减少对象数量,减少内存开销。适配器模式对目标函数进行数据参数转化,使其符合目标函数所需要的格式。 设计模式 单例模式 JS的单例模式有别于传统面向对象语言的单例模式,js作为一门无类的语言。使用全局变量的模式来实现单例模式思想。js里面的单例又分为普通单例和惰性单例,惰性单例指的是只有这个实例...

    Panda 评论0 收藏0
  • 前端开发中常用的javascript设计模式

    摘要:代理模式,迭代器模式,单例模式,装饰者模式最少知识原则一个软件实体应当尽可能少地与其他实体发生相互作用。迭代器模式可以将迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即不用关心对象内部构造也可以按顺序访问其中的每个元素。 接手项目越来越复杂的时候,有时写完一段代码,总感觉代码还有优化的空间,却不知道从何处去下手。设计模式主要目的是提升代码可扩展性以及可阅读性。 本文主要以例子的...

    赵春朋 评论0 收藏0
  • JavaScript 中常见设计模式整理

    摘要:开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式。本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知。 showImg(https://segmentfault.com/img/remote/1460000014919705?w=640&h=280); 开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设...

    Nosee 评论0 收藏0
  • javascript中的设计模式(一)

    摘要:模式迭代器模式顾名思义,迭代器可以将对于一个聚合对象内部元素的访问与业务逻辑分离开。模式组合模式组合模式将对象组合成树形结构,以表示层级结构。重点是,叶结点与中间结点有统一借口。本文总结自设计模式与开发实践,曾探著 模式1 - 单例模式 单例模式的核心是确保只有一个实例,并且提供全局访问。 特点: 满足单一职责原则 : 使用代理模式,不在构造函数中判断是否已经创建过该单例; 满足惰...

    chaosx110 评论0 收藏0
  • JavaScript设计模式系列二:单例模式

    摘要:什么时候需要用到单例模式呢其实单例模式在日常开发中的使用非常的广泛,例如各种浮窗像登录浮窗等,无论我们点击多少次,都是同一个浮窗,浮窗从始至终只创建了一次。这种场景就十分适合运用单例模式。 单例模式 什么是单例模式? 单例模式的定义:一个类仅有一个实例,并且可以在全局访问。什么时候需要用到单例模式呢?其实单例模式在日常开发中的使用非常的广泛,例如各种浮窗、像登录浮窗等,无论我们点击多少...

    SegmentFault 评论0 收藏0

发表评论

0条评论

0xE7A38A

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<