首页 > 代码库 > js单例模式
js单例模式
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
为了帮助大家更好地理解单例模式,大家可以结合下面的类图来进行理解,以及后面也会剖析单例模式的实现思路:
下面就看看具体的实现代码(看完之后你会惊讶道:真是这样的!):
/// <summary> /// 单例模式的实现 /// </summary> public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 定义私有构造函数,使外界不能创建该类实例 private Singleton() { } /// <summary> /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 /// </summary> /// <returns></returns> public static Singleton GetInstance() { // 如果类的实例不存在则创建,否则直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
上面的单例模式的实现在单线程下确实是完美的,然而在多线程的情况下会得到多个Singleton实例,因为在两个线程同时运行GetInstance方 法时,此时两个线程判断(uniqueInstance ==null)这个条件时都返回真,此时两个线程就都会创建Singleton的实例,这样就违背了我们单例模式初衷了,既然上面的实现会运行多个线程执 行,那我们对于多线程的解决方案自然就是使GetInstance方法在同一时间只运行一个线程运行就好了,也就是我们线程同步的问题了(对于线程同步大家也可以参考我线程同步的文章),具体的解决多线程的代码如下:
/// <summary> /// 单例模式的实现 /// </summary> public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 定义一个标识确保线程同步 private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例 private Singleton() { } /// <summary> /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 /// </summary> /// <returns></returns> public static Singleton GetInstance() { // 当第一个线程运行到这里时,此时会对locker对象 "加锁", // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象"解锁" lock (locker) { // 如果类的实例不存在则创建,否则直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } return uniqueInstance; } }
上面这种解决方案确实可以解决多线程的问题,但是上面代码对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于 这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(uniqueInstance==null)为假,此时 完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能,为了改进上面实现方式的缺陷,我们只需要在lock语句前 面加一句(uniqueInstance==null)的判断就可以避免锁所增加的额外开销,这种实现方式我们就叫它 “双重锁定”,下面具体看看实现代码的:
/// <summary> /// 单例模式的实现 /// </summary> public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 定义一个标识确保线程同步 private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例 private Singleton() { } /// <summary> /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 /// </summary> /// <returns></returns> public static Singleton GetInstance() { // 当第一个线程运行到这里时,此时会对locker对象 "加锁", // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象"解锁" // 双重锁定只需要一句判断就可以了 if (uniqueInstance == null) { lock (locker) { // 如果类的实例不存在则创建,否则直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
理解完了单例模式之后,菜鸟又接着问了:.NET FrameWork类库中有没有单例模式的实现呢?
经过查看,.NET类库中确实存在单例模式的实现类,不过该类不是公开的,下面就具体看看该类的一个实现的(该类具体存在于System.dll程序集,命名空间为System,大家可以用反射工具Reflector去查看源码的):
// 该类不是一个公开类 // 但是该类的实现应用了单例模式 internal sealed class SR { private static SR loader; internal SR() { } // 主要是因为该类不是公有,所以这个全部访问点也定义为私有的了 // 但是思想还是用到了单例模式的思想的 private static SR GetLoader() { if (loader == null) { SR sr = new SR(); Interlocked.CompareExchange<SR>(ref loader, sr, null); } return loader; } // 这个公有方法中调用了GetLoader方法的 public static object GetObject(string name) { SR loader = GetLoader(); if (loader == null) { return null; } return loader.resources.GetObject(name, Culture); } }
js单例模式:
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‘; };
js通用的惰性单列 :
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‘).onclcik = function(){ var loginLayer = createSingleLoginLayer(); loginLayer.style.display = ‘block‘; }; 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‘; };
js单例模式