设计模式系列笔记-单例模式

写在前面:本系列文章内容为《JavaScript设计模式与开发实践》一书学习笔记,感谢作者曾探

单例模式

定义:保证一个类仅有一个实例,并可以全局访问该实例
举例:线程池、全局缓存、window对象等,或者全局的弹框组件、登录组件等

1.实现单例模式

思路:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象
示例:

const 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;
};
const a = Singleton.getInstance('sven1');
const b = Singleton.getInstance('sven2');
alert(a === b); // true

2.透明的单例模式

透明的单例模式: 用户从这个单例类中创建对象的时候,可以像使用其他任何普通类一样

// 透明的单例模式
const CreateDiv = (function() {
  let instance;
  const CreateDiv = function(html) {
    if (instance) {
      return instance;
    }
    this.html = html;
    this.init();
    return instance = this;
  };
  CreateDiv.prototype.init = function() {
    const div = document.createElement('div');
    div.innerHTML = this.html;
    document.body.appendChild(div);
  };
  return CreateDiv;
})();
const a = new CreateDiv('sven1');
const b = new CreateDiv('sven2');
alert(a === b); // true

但是这种写法也有缺点:CreateDiv 的构造函数实际上负责了两件事情。第一是创建对象和执行初始化init 方法,第二是保证只有一个对象,这种多重职责使这个构造函数看起来很奇怪

3.用代理实现单例模式

通过引入代理的方式,我们改造上面的例子,跟之前不同的是,现在我们把负责管理单例的逻辑移到了代理类proxySingletonCreateDiv 中。这样一来,CreateDiv 就变成了一个普通的类,它跟proxySingletonCreateDiv 组合起来可以达到单例模式的效果。

// 代理实现单例模式
class CreateDiv {
  constructor(html) {
    this.html = html;
    this.init();
  }
};
CreateDiv.prototype.init = function() {
  const div = document.createElement('div');
  div.innerHTML = this.html;
  document.body.appendChild(div);
};
// 引入代理方法proxySingletonCreateDiv:
const ProxySingletonCreateDiv = (function() {
  let instance;
  return (html) => {
    if (!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  }
})();
const a = ProxySingletonCreateDiv('sven1');
const b = ProxySingletonCreateDiv('sven2');
alert(a === b);

4.惰性单例

惰性单例指的是在需要的时候才创建对象实例
以网页QQ的登录框举例

// 惰性单例
var createLoginLayer = (function() {
  var div;
  return function() {
    if (!div) {
      div = document.createElement('div');
      div.innerHTML = '我是登录浮窗';
      div.style.display = 'none';
      document.body.appendChild(div);
    }
    return div;
  }
})();
document.getElementById('loginBtn').onclick = function() {
  var loginLayer = createLoginLayer();
  loginLayer.style.display = 'block';
};

这种方案存在的问题:

  • 这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在 createLoginLayer对象内部
  • 如果我们下次需要创建页面中唯一的 iframe,或者 script 标签,用来跨域请求数据,就必须得如法炮制,把 createLoginLayer 函数几乎照抄一遍

5.通用的惰性单例

管理单例的逻辑其实是完全可以抽象出来的,这个逻辑始终是一样的:用一个变量来标志是否创建过对象,如果是,则在下次直接返回这个已经创建好的对象
现在我们就把如何管理单例的逻辑从原来的代码中抽离出来,这些逻辑被封装在 getSingle
函数内部,创建对象的方法 fn 被当成参数动态传入 getSingle 函数

var getSingle = function(fn) {
  var result;
  return function() {
    return result || (result = fn.apply(this, arguments));
  }
};

接下来将用于创建登录浮窗的方法用参数 fn 的形式传入 getSingle,我们不仅可以传入createLoginLayer,还能传入 createScript、createIframe、createXhr 等。之后再让 getSingle 返回一个新的函数,并且用一个变量 result 来保存 fn 的计算结果。result 变量因为身在闭包中,它永远不会被销毁。在将来的请求中,如果 result 已经被赋值,那么它将返回这个值。代码如下:

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';
};

我们再试试创建唯一的 iframe 用于动态加载第三方页面:

var createSingleIframe = getSingle(function() {
  var iframe = document.createElement('iframe');
  document.body.appendChild(iframe);
  return iframe;
});
document.getElementById('loginBtn').onclick = function() {
  var loginLayer = createSingleIframe();
  loginLayer.src = 'http://baidu.com';
};

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

6.小节

在 getSinge 函数中,实际上也提到了闭包和高阶函数的概念。单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个,使用了代理模式和单一职责原则,创建对象和管理单例的职责被分布在两个不同的方法中

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容

  • 单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式是一种常用的模式,有一些对象我们...
    yufawu阅读 614评论 0 7
  • javascript设计模式与开发实践 设计模式 每个设计模式我们需要从三点问题入手: 定义 作用 用法与实现 单...
    穿牛仔裤的蚊子阅读 4,047评论 0 13
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,058评论 1 10
  • 前言 单例模式的定义:保证一个类仅仅有一个实例,并提供一个访问他的全局访问点。意义:有的时候,一些对象我们仅仅需要...
    kim_jin阅读 1,732评论 0 1
  • 1 单例模式 单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点; 1.1 实现单例模式 用...
    Haleng阅读 267评论 0 0