确保一个类仅有一个实例,并提供一个访问它的全局访问点。html
好比线程池、全局缓存等。咱们所熟知的浏览器的window对象就是一个单例,在JavaScript开发中,对于这种只须要一个的对象,咱们的实现每每使用单例。git
通常咱们是这样实现单例的,用一个变量来标志当前的类已经建立过对象,若是下次获取当前类的实例时,直接返回以前建立的对象便可。代码以下:github
// 定义一个类 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);
这种单例模式我之前用过一次,可是使用起来很别扭,我也见过别人用这种方式实现过走马灯的效果,由于走马灯在咱们的应用中绝大多数只有一个。缓存
这里先说一下为何感受不对劲,由于在这个单例的构造函数中一共干了两件事,一个是建立对象并打印实例名字,另外一个是保证只有一个实例对象。这样代码量大的化不方便管理,应该尽可能作到职责单一。闭包
咱们一般会将代码改为下面这个样子:app
// 单例构造函数 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);
这种实现方式咱们就比较熟悉了,咱们在开发中常常会使用中间类,经过它来实现原类所不具备的特殊功能。有的人把这种实现方式叫作代理,这的确是单例模式的一种应用,稍后将在代理模式进行详解。dom
说了这么多咱们仍是在围绕着传统的单例模式实如今进行讲解,那么具备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指向,使用 ||
预算简化代码的书写。