定义

确保一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式使用的场景

比如线程池、全局缓存等。我们所熟知的浏览器的window对象就是一个单例,在JavaScript开发中,对于这种只需要一个的对象,我们的实现往往使用单例。

实现单例模式 (不透明的)

一般我们是这样实现单例的,用一个变量来标志当前的类已经创建过对象,如果下次获取当前类的实例时,直接返回之前创建的对象即可。代码如下:

// 定义一个类
function Singleton(name) {
this.name = name;
this.instance = null;
}
// 原型扩展类的一个方法getName()
Singleton.prototype.getName = function() {
console.log(this.name)
};
// 获取类的实例
Singleton.getInstance = function(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance
}; // 获取对象1
var a = Singleton.getInstance('a');
// 获取对象2
var b = Singleton.getInstance('b');
// 进行比较
console.log(a === b);

我们也可以使用闭包来实现:

function Singleton(name) {
this.name = name;
}
// 原型扩展类的一个方法getName()
Singleton.prototype.getName = function() {
console.log(this.name)
};
// 获取类的实例
Singleton.getInstance = (function() {
var instance = null;
return function(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance
}
})(); // 获取对象1
var a = Singleton.getInstance('a');
// 获取对象2
var b = Singleton.getInstance('b');
// 进行比较
console.log(a === b);

这个单例实现获取对象的方式经常见于新手的写法,这种方式获取对象虽然简单,但是这种实现方式不透明。知道的人可以通过 Singleton.getInstance() 获取对象,不知道的需要研究代码的实现,这样不好。这与我们常见的用 new 关键字来获取对象有出入,实际意义不大。

实现单例模式 (透明的)

var Singleton = (function(){
var instance;
var CreateSingleton = function (name) {
this.name = name; if(instance) {
return instance;
}
// 打印实例名字
this.getName(); // instance = this;
// return instance;
return instance = this;
}
// 获取实例的名字
CreateSingleton.prototype.getName = function() {
console.log(this.name)
} return CreateSingleton;
})();
// 创建实例对象1
var a = new Singleton('a');
// 创建实例对象2
var b = new Singleton('b'); console.log(a===b);

这种单例模式我以前用过一次,但是使用起来很别扭,我也见过别人用这种方式实现过走马灯的效果,因为走马灯在我们的应用中绝大多数只有一个。

这里先说一下为什么感觉不对劲,因为在这个单例的构造函数中一共干了两件事,一个是创建对象并打印实例名字,另一个是保证只有一个实例对象。这样代码量大的化不方便管理,应该尽量做到职责单一。

我们通常会将代码改成下面这个样子:

// 单例构造函数
function CreateSingleton (name) {
this.name = name;
this.getName();
}; // 获取实例的名字
CreateSingleton.prototype.getName = function() {
console.log(this.name)
};
// 单例对象
var Singleton = (function(){
var instance;
return function (name) {
if(!instance) {
instance = new CreateSingleton(name);
}
return instance;
}
})(); // 创建实例对象1
var a = new Singleton('a');
// 创建实例对象2
var b = new Singleton('b'); console.log(a===b);

这种实现方式我们就比较熟悉了,我们在开发中经常会使用中间类,通过它来实现原类所不具有的特殊功能。有的人把这种实现方式叫做代理,这的确是单例模式的一种应用,稍后将在代理模式进行详解。

说了这么多我们还是在围绕着传统的单例模式实现在进行讲解,那么具有JavaScript特色的单例模式是什么呢。

JavaScript单例模式

在我们的开发中,很多同学可能并不知道单例到底是什么,应该如何使用单例,但是他们所写的代码却刚好满足了单例模式的要求。如要实现一个登陆弹窗,不管那个页面或者在页面的那个地方单击登陆按钮,都会弹出登录窗。一些同学就会写一个全局的对象来实现登陆窗口功能,是的,这样的确可以实现所要求的登陆效果,也符合单例模式的要求,但是这种实现其实是一个巧合,或者一个美丽的错误。由于全局对象,或者说全局变量正好符合单例的能够全局访问,而且是唯一的。但是我们都知道,全局变量是可以被覆盖的,特别是对于初级开发人员来说,刚开始不管定义什么基本都是全局的,这样的好处是方便访问,坏处是一不留意就会引起冲突,特别是在做一个团队合作的大项目时,所以成熟的有经验的开发人员尽量减少全局的声明。

而在开发中我们避免全局变量污染的通常做法如下:

  • 全局命名空间
  • 使用闭包

它们的共同点是都可以定义自己的成员、存储数据。区别是全局命名空间的所有方法和属性都是公共的,而闭包可以实现方法和属性的私有化。

惰性单例模式

说实话,在我下决心学习设计模式之前我并不知道,单例模式还分惰性单例模式,直到我看了曾探大神的《JvaScript设计模式与开发实践》后才知道了还有惰性单例模式,那么什么是惰性单例模式呢?在说惰性单例模式之前,请允许我先说一个我们都知道的lazyload加载图片,它就是惰性加载,只当含有图片资源的dom元素出现在媒体设备的可视区时,图片资源才会被加载,这种加载模式就是惰性加载;还有就是下拉刷新资源也是惰性加载,当你触发下拉刷新事件资源才会被加载等。而惰性单例模式的原理也是这样的,只有当触发创建实例对象时,实例对象才会被创建。这样的实例对象创建方式在开发中很有必要的。

就如同我们刚开始介绍的用 Singleton.getInstance 创建实例对象一样,虽然这种方式实现了惰性单例,但是正如我们刚开始说的那样这并不是一个好的实现方式。下面就来介绍一个好的实现方式。

遮罩层相信大家对它都不陌生。它在开发中比较常见,实现起来也比较简单。在每个人的开发中实现的方式不尽相同。这个最好的实现方式还是用单例模式。有的人实现直接在页面中加入一个div然后设置display为none,这样不管我们是否使用遮罩层页面都会加载这个div,如果是多个页面就是多个div的开销;也有的人使用js创建一个div,当需要时就用将其加入到body中,如果不需要就删除,这样频繁地操作dom对页面的性能也是一种消耗;还有的人是在前一种的基础上用一个标识符来判断,当遮罩层是第一次出现就向页面添加,不需要时隐藏,如果不是就是用前一次的添加的。

实现代码如下:

// html

<button id="btn">click it</button>

// js
var createMask = (function() {
var mask;
return function() {
if(!mask) {
// 创建div元素
var mask = document.createElement('div');
// 设置样式
mask.style.position = 'fixed';
mask.style.top = '0';
mask.style.right = '0';
mask.style.bottom = '0';
mask.style.left = '0';
mask.style.opacity = '';
mask.style.display = 'none';
document.body.appendChild(mask);
} return mask;
}
})(); document.getElementById('btn').onclick = function() {
var maskLayer = createMask();
maskLayer.style.display = 'block';
}

我们发现在开发中并不会单独使用遮罩层,遮罩层和弹出窗是经常结合在一起使用,前面我们提到过登陆弹窗使用单例模式实现也是最适合的。那么我们是不是要将上面的代码拷贝一份呢?当然我们还有好的实现方式,那就是将上面单例中代码变化的部分和不变的部分,分离开来。

代码如下:

var singleton = function(fn) {
var instance;
return function() {
return instance || (instance = fn.apply(this, arguments));
}
};
// 创建遮罩层
var createMask = function(){
// 创建div元素
var mask = document.createElement('div');
// 设置样式
mask.style.position = 'fixed';
mask.style.top = '0';
mask.style.right = '0';
mask.style.bottom = '0';
mask.style.left = '0';
mask.style.opacity = 'o.75';
mask.style.backgroundColor = '#000';
mask.style.display = 'none';
mask.style.zIndex = '98';
document.body.appendChild(mask);
// 单击隐藏遮罩层
mask.onclick = function(){
this.style.display = 'none';
}
return mask;
}; // 创建登陆窗口
var createLogin = function() {
// 创建div元素
var login = document.createElement('div');
// 设置样式
login.style.position = 'fixed';
login.style.top = '50%';
login.style.left = '50%';
login.style.zIndex = '100';
login.style.display = 'none';
login.style.padding = '50px 80px';
login.style.backgroundColor = '#fff';
login.style.border = '1px solid #ccc';
login.style.borderRadius = '6px'; login.innerHTML = 'login it'; document.body.appendChild(login); return login;
}; document.getElementById('btn').onclick = function() {
var oMask = singleton(createMask)();
oMask.style.display = 'block';
var oLogin = singleton(createLogin)();
oLogin.style.display = 'block';
var w = parseInt(oLogin.clientWidth);
var h = parseInt(oLogin.clientHeight);
}

在上面的实现中将单例模式的惰性实现部分提取出来,实现了惰性实现代码的复用,其中使用apply改变改变了fn内的this指向,使用 || 预算简化代码的书写。

[转] JavaScript 单例模式的更多相关文章

  1. javascript单例模式的理解

    javascript单例模式的理解 阅读目录 理解单例模式 使用代理实现单例模式 理解惰性单例 编写通用的惰性单例 单例模式使用场景 回到顶部 理解单例模式 单例模式的含义是: 保证一个类只有一个实例 ...

  2. 浅析Javascript单例模式

    定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点 .就想我们在开发中有些对象只需要一个,例如window对象. 1. 实现单例模式 var Singleton = function( nam ...

  3. Javascript单例模式概念与实例

    前言 和其他编程语言一样,Javascript同样拥有着很多种设计模式,比如单例模式.代理模式.观察者模式等,熟练运用Javascript的设计模式可以使我们的代码逻辑更加清晰,并且更加易于维护和重构 ...

  4. 轻松掌握:JavaScript单例模式

    单例模式 定义:保证一个对象(类)仅有一个实例,并提供一个访问它的全局访问点: 实现原理:利用闭包来保持对一个局部变量的引用,这个变量保存着首次创建的唯一的实例; 主要用于:全局缓存.登录浮窗等只需要 ...

  5. 深入理解 JavaScript 单例模式 (Singleton Pattern)

    概念 单例模式,也叫单子模式,是一种常用的软件设计模式.在应用这个模式时,单例对象的类必须保证只有一个实例存在. 核心:确保只有一个实例,并提供全局访问. 实现思路 一个类能返回对象一个引用(永远是同 ...

  6. javascript单例模式及开发实践

    定义: 保证一个对象(类)仅有一个实例,并提供一个访问它的全局访问点: 实现原理: 利用闭包来保持对一个局部变量的引用,这个变量保存着首次创建的唯一的实例; 主要用于: 全局缓存.登录浮窗等只需要唯一 ...

  7. JavaScript单例模式

    一.什么是单例 意思是指获取的对象只有一份. 二.最通用的单例 任何时刻获取SingLeton.instance都是同一个对象 var SingLeton={ instance:{ property: ...

  8. JavaScript 单例模式实现

    Singleton模式指的是调用一个类,任何时候返回的都是同一个实例. 对于Node来说,模块文件可以看成是一个类.怎么保证每次执行这个模块文件,返回的都是同一个实例呢? 很容易想到,可以把实例放到顶 ...

  9. javascript单例模式(懒汉 饿汉)

    第一种:懒汉模式 var Singleton=(function(){ var instantiated; //比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢 functi ...

随机推荐

  1. 全面接触PDF:最好用的PDF软件汇总(转)

    全面接触PDF:最好用的PDF软件汇总(2010-12-07更新): http://xbeta.info/pdf-software.htm 比较全面的c#帮助类,各种功能性代码: https://gi ...

  2. struts2框架之类型转换(参考第二天学习笔记)

    类型转换 1. 什么是类型转换 刚才学习了封装请求参数,把表单数据封装到Action(模型)的属性中.表单中的数据都是String类型,但Action(模型)的属性不一定什么类型. 将来我们还需要数据 ...

  3. Luogu:P1600 天天爱跑步

    来一发清新的80行 树剖 $LCA$ + 树上差分 题解. -----from Judge 本题题意大概是给出一棵 n 个节点的树以及 m 条有向路径, 并且每个点 i 都有一个权值 $w[i]$,如 ...

  4. CF449C:Jzzhu and Apples

    题意简述 给出正整数n,你要把1-n之间的正整数分成尽可能多组,使得每一组两个数的最大公约数大于1;输出能分成最多组的个数,并按任意顺序输出每组的两个数. 很妙的一道题. 首先我们考虑去处理每个质数的 ...

  5. 前端 ---- js 模拟百度导航栏滚动案例

    模拟百度导航栏滚动监听 代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta chars ...

  6. 【转】Jmeter中使用CSV Data Set Config参数化不重复数据执行N遍

    Jmeter中使用CSV Data Set Config参数化不重复数据执行N遍 要求: 今天要测试上千条数据,且每条数据要求执行多次,(模拟多用户多次抽奖) 1.用户id有175个,且没有任何排序规 ...

  7. [MySQL]理解关系型数据库4个事务隔离级别

    概述 SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的.低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销. 1. Read Uncommi ...

  8. 《Spring5官方文档》新功能(4,3)

    <Spring5官方文档>新功能 原文链接 译者:supriseli Spring框架的新功能 这一章主要提供Spring框架新的功能和变更. 升级到新版本的框架可以参考.Spring g ...

  9. html5中如何去掉input type date默认

    html5中如何去掉input type date默认样式 2.对日期时间控件的样式进行修改目前WebKit下有如下9个伪元素可以改变日期控件的UI:::-webkit-datetime-edit – ...

  10. 洛谷P4336 [SHOI2016]黑暗前的幻想乡 [Matrix-Tree定理,容斥]

    传送门 思路 首先看到生成树计数,想到Matrix-Tree定理. 然而,这题显然是不能Matrix-Tree定理硬上的,因为还有每个公司只能建一条路的限制.这个限制比较恶心,尝试去除它. 怎么除掉它 ...