javascript设计模式与开发实践阅读笔记(4)——单例模式
定义
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
具体来说,就是保证有些对象有且只有一个,比如线程池、全局缓存、浏览器中的window 对象等。在js中单例模式用途很广,比如登录悬浮窗,我希望无论我点击多少次这个浮窗都只会被创建一次,这里就可以用单例模式。
1.实现单例模式
思路:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象;如果否就创建出那个对象。
var Singleton = function( name ){ //构造函数
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function(){ //构造器原形上添加方法,可以获得对象的name属性
alert ( this.name );
};
Singleton.getInstance = function( name ){
if ( !this.instance ){ //如果不存在对象实例
this.instance = new Singleton( name ); //创建对象实例
}
return this.instance; //返回对象实例
};
var a = Singleton.getInstance( 'sven1' );
var b = Singleton.getInstance( 'sven2' );
alert ( a === b ); // true
console.log(a.name); // sven1
console.log(b.name); // seven1
console.log(a.instance); // null
console.log(b.instance); // null
console.log(window.instance); // Singleton {name: "sven1", instance: null}
这是书里的例子,从最下面的测试来看,这个例子其实并不太好,比较容易让人误会。我们改造一下,其实是一个意思:
var Singleton = function( name ){ //构造函数
this.name = name;
this.instance = null; //无效的一个属性
};
Singleton.prototype.getName = function(){ //构造器原型上添加方法,可以获得对象的name属性
alert ( this.name );
};
create = function( name ){ //全局创建对象的函数
if ( !this.sing ){ //这里的this指向的是window,即全局,如果全局不存在sing对象
this.sing = new Singleton( name ); //创建sing对象
}
return this.sing; //返回sing对象
};
var a = create( 'sven1' );
var b = create( 'sven2' );
alert ( a === b ); // true
console.log(a.name); // sven1
console.log(b.name); // seven1
console.log(a.instance); // null
console.log(b.instance); // null
console.log(window.sing); // Singleton {name: "sven1", instance: null}
书里还有第二个创建单例模式的例子,如下:
var Singleton = function( name ){ //构造函数
this.name = name;
};
Singleton.prototype.getName = function(){ //原型上添加一个方法,可以返回对象的name属性
alert ( this.name );
};
Singleton.getInstance = (function(){ //全局的一个自执行函数,自执行是为了把返回的函数字面量赋给Singleton.getInstance
var instance = null; //函数内部变量,但用闭包保存起来
return function( name ){
if ( !instance ){ //如果没有创建过对应对象,即函数的这个内部变量没有被赋值
instance = new Singleton( name ); //创建对象
}
return instance; //返回对象
}
})();
这个例子比上面的要好不少,作者表示之所以用Singleton.getInstance这样的命名,是故意的,故意用这样的方式来创建单例类,来和通过new XXX的方式获取到的对象区分开,创建单例类必须要保存单例,所以需要定义一个全局变量,这种方式其实很不友好。
2.改进单例模式
现在的目标是实现一个“透明”的单例类,利用闭包保存单例,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样,这种方式较上面友好许多。
var CreateDiv = (function(){ //匿名自执行函数,同时返回一个函数,创造了一个闭包环境
var instance; //利用闭包存储的对象实例
var CreateDiv = function( html ){ //返回的函数
if ( instance ){ //如果对象存在,返回对象
return instance;
}
this.html = html; //不存在就赋值,创建
this.init();
return instance = this; //new的时候返回实例
};
CreateDiv.prototype.init = function(){ //原型上绑定的方法
var div = document.createElement( 'div' );
div.innerHTML = this.html;
document.body.appendChild( div );
};
return CreateDiv;
})(); var a = new CreateDiv( 'sven1' );
var b = new CreateDiv( 'sven2' );
alert ( a === b ); // true
缺点:为了把instance封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的Singleton 构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服。
var CreateDiv = function( html ){
if ( instance ){
return instance;
}
this.html = html;
this.init();
return instance = this;
};
观察这段构造函数,它实际上做了两件事,第一是创建对象和执行初始化init方法,第二是保证只有一个对象。这其实是种不好的做法,应该尽量遵循“单一职责原则”,假设我们某天需要利用这个类,在页面中创建千千万万的div,即要让这个类从单例类变成一个普通的可产生多个实例的类,那我们必须得改写CreateDiv 构造函数,把控制创建唯一对象的那一段去掉,这种修改会给我们带来不必要的烦恼。
3.用代理实现单例模式
通过引入代理类的方式,来解决上面提到的问题。
在CreateDiv 构造函数中,把负责管理单例的代码移除出去,使它成为一个普通的创建div的类,如下:
var CreateDiv = function( html ){
this.html = html;
this.init();
};
CreateDiv.prototype.init = function(){
var div = document.createElement( 'div' );
div.innerHTML = this.html;
document.body.appendChild( div );
};
接下来引入代理类SingletonCreateDiv:
var SingletonCreateDiv = (function(){
var instance;
return function( html ){
if ( !instance ){
instance = new CreateDiv( html );
}
return instance;
}
})();
var a = new SingletonCreateDiv( 'sven1' );
var b = new SingletonCreateDiv( 'sven2' );
alert ( a === b ); //true
通过引入代理类的方式,完成了一个单例模式的编写,跟之前不同的是,现在我们把负责管理单例的逻辑移到了代理类SingletonCreateDiv中。这样一来,CreateDiv就变成了一个普通的类,它跟SingletonCreateDiv组合起来可以达到单例模式的效果。本例是缓存代理的应用之一,这样写的好处是毋庸置疑的。
4.JavaScript中的单例模式
前面几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从“类”中创建而来。在以类为中心的语言中,这是很自然的做法,对象总是从类中创建而来的。而在JavaScript 中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它先创建一个“类”呢?这无异于穿棉衣洗澡,传统的单例模式实现在JavaScript中并不适用。
单例模式的核心是确保只有一个实例,并提供全局访问。
全局变量不是单例模式,但在JavaScript 开发中,我们经常会把全局变量当成单例来使用。
var a = {};
当用这种方式创建对象a 时,对象a 确实是独一无二的。如果a变量被声明在全局作用域下,则我们可以在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所当然的。这样就满足了单例模式的两个条件。
但这样明显会污染全局的命名空间,有这样几种方式可以相对降低其它全局变量带来的命名污染:
(1)使用命名空间
变量放到了命名空间内,成为了命名空间的属性
var namespace1 = {
a: function(){
alert (1);
},
b: function(){
alert (2);
}
};
(2)使用闭包封装私有变量
这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信。
var user = (function(){
var __name = 'sven',
__age = 29;
return {
getUserInfo: function(){
return __name + '-' + __age;
}
}
})();
5.惰性单例
惰性单例指的是在需要的时候才创建对象实例。惰性单例是单例模式的重点,这种技术在实际开发中非常有用,有用的程度可能超出了我们的想象。
首先我们想要创建一个悬浮窗用于登录,被一个点击事件触发,且悬浮窗唯一:
var loginLayer = (function(){ //loginLayer就是单例对象,这里没有用类的方式创建,而是直接给了一个全局对象
var div = document.createElement( 'div' );
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild( div );
return div;
})(); document.getElementById( 'loginBtn' ).onclick = function(){
loginLayer.style.display = 'block';
};
这个方法缺点很明显,如果我们一直不去登录,由于这个悬浮窗是早就创建好的,这样就有可能浪费一个DOM节点。应该用户点击之后才创建:
var createLoginLayer = function(){
var 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';
};
这次达到了惰性的目的,但是失去了单例效果,每次点击都会创建一个悬浮窗。我们可以用一个变量来判断是否已经创建过登录浮窗:
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';
};
这次代码虽然实现了功能,但是又违反了之前提过的“单一职责原则”,我们需要把管理单例的代码抽离出来:
var getSingle = function( fn ){ //管理单例,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 bindEvent = getSingle(function(){
document.getElementById( 'div1' ).onclick = function(e){
alert ( 'e.target' );
}
return true; //单例需要接收一个返回值
});
var render = function(){
console.log( '开始渲染列表' );
bindEvent();
}; render(); //这里即使运行了三次,但只绑定了一次,不会浪费性能
render();
render();
总结
单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个。将管理单例和创建对象的方法分开是种很好的思路。
javascript设计模式与开发实践阅读笔记(4)——单例模式的更多相关文章
- javascript设计模式与开发实践阅读笔记(8)——观察者模式
发布-订阅模式,也叫观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 在JavaScript开发中,我们一般用事件模型来替代传统的观察者模式. ...
- javascript设计模式与开发实践阅读笔记(7)——迭代器模式
迭代器模式:指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. 迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺 ...
- javascript设计模式与开发实践阅读笔记(6)——代理模式
代理模式:是为一个对象提供一个代用品或占位符,以便控制对它的访问. 代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对 ...
- javascript设计模式与开发实践阅读笔记(5)——策略模式
策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 我的理解就是把各种方法封装成函数,同时存在一个可以调用这些方法的公共函数.这样做的好处是可以消化掉内部的分支判断,使代码效率 ...
- javascript设计模式与开发实践阅读笔记(9)——命令模式
命令模式:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 说法很复 ...
- javascript设计模式与开发实践阅读笔记(11)—— 模板方法模式
模板方法模式: 由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类.通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序.子类通过继承这个抽象类,也继 ...
- JavaScript设计模式与开发实践——读书笔记1.高阶函数(上)
说来惭愧,4个多月未更新了.4月份以后就开始忙起来了,论文.毕设.毕业旅行等七七八八的事情占据了很多时间,毕业之后开始忙碌的工作,这期间一直想写博客,但是一直没能静下心写.这段时间在看<Java ...
- 《JavaScript设计模式与开发实践》笔记第八章 发布-订阅模式
第八章 发布-订阅模式 发布-订阅模式描述 发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 发布-订阅模式可以广泛应用于 ...
- JavaScript设计模式与开发实践——读书笔记1.高阶函数(下)
上部分主要介绍高阶函数的常见形式,本部分将着重介绍高阶函数的高级应用. 1.currying currying指的是函数柯里化,又称部分求值.一个currying的函数会先接受一些参数,但不立即求值, ...
随机推荐
- ASP.NET 创建网站地图
很多个人站长会使用工具来生成自己网站的站点地图,这样做的缺点在于网站的 sitemap 不能及时的得到更新.当我们发表了一篇新文章时,应该对网站的地图进行更新,并通知搜索引擎网站地图已经发生了改变! ...
- Daily Scrum 12.3
今日完成任务: 与安卓组进行商量对数据库修改的方案.现在在等他们最终确认,确认之后进行整理以及源代码的调试. 对资源功能的代码进行阅读. 遇到困难: 关于整合,爬虫组爬到的内容和网站定位有所不符,所以 ...
- meta http-equiv='refresh' 解读
页面定期刷新,如果加url的,则会重新定向到指定的网页,content后面跟的是时间(单位秒), 把这句话加到指定网页的<head></head>里 一般也用在实时性很强的应用 ...
- Java 第五章 循环结构1
循环结构 1 while 循环结构 ,do- while 循环结构 . 循环结构: 必须满足两个条件 . 1,循环条件 和 循环 操作 ! while 循环 特点:先判断,再执行 , 编码规范:缩进, ...
- Cocos与Cocos2d-x协作教程——多分辨率适配
http://www.cocoachina.com/bbs/read.php?tid-288123.html Cocos v2.1开始新增了一种新的多分辨率适配方案:流式布局. 这种布局相比Cocos ...
- N皇后问题(位运算实现)
本文参考Matrix67的位运算相关的博文. 顺道列出Matrix67的位运算及其使用技巧 (一) (二) (三) (四),很不错的文章,非常值得一看. 主要就其中的N皇后问题,给出C++位运算实现版 ...
- Python成长笔记 - 基础篇 (一)python简介
一.Python介绍 Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/),由吉多·范罗苏姆(Guido van Rossum)于1989年发明,第一个公开发行版发行于1991 ...
- sql命令查看,清楚mysql bin日志
查看二进制日志文件 mysql> SHOW BINLOG EVENTS \G; mysql> SHOW MASTER LOGS; 清除二进制日志文件 mysql> PURGE { M ...
- Android系统的常用权限
1.ACCES_NETWORK_STATE 允许应用程序获取网络状态信息的权限 2.ACCESS_WIFI_STATE ...
- 使用的组件:ckeditor
老牌Web文本编辑器,无需多言. 官网地址:http://ckeditor.com/