单例模式 单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点 注意: 在单线程的js环境里里不存在线程安全的问题
实现单例模式 实现单例模式并不复杂,只不过是用一个变量来标志当前是否已经为某个类型创建过对象,如果是,则下一次获取该类的实例时,直接返回之前创建的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var Singleton = function (name ) { this .name = name; } Singleton.instance = null ; Singleton.prototype.getName = function ( ) { return this .name; } Singleton.singleton = function (name ) { if (!this .instance) { this .instance = new Singleton(name); } return this .instance; } var a = Singleton.singleton("a" );var b = Singleton.singleton("b" );console .log(a === b);
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var Singleton = function (name ) { this .name = name; } Singleton.prototype.getName = function ( ) { return this .name; } Singleton.singleton = (function ( ) { var instance = null ; return function (name ) { if (!instance) { instance = new Singleton(name); } return this .instance; } })(); var a = Singleton.singleton("a" );var b = Singleton.singleton("b" );console .log(a === b)
这种写法的问题时具有不透明性,而且必须要用Singleton.singleton来获取对象。
透明的单例模式 现在的目的是实现一个透明的单例类,用户创建对象的时候,可以像使用其他任何普通类一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var CreateDiv = (function ( ) { var instance = null ; var CreateDiv = function (html ) { if (instance) { return instance; } this .html = html; this .init(); return instance = this ; }; CreateDiv.prototype.init = function ( ) { var div = document .createElement("div" ); div.innerHTML = this .html; document .body.appendChild(div); }; return CreateDiv; })(); var a = new CreateDiv("a" );var b = new CreateDiv("b" );console .log(a === b)
CreateDiv做了两件事情:1.保证只有一个对象。 2.创建对象和执行init方法。 这就违背了单一职责原则。 假设某天我们要创建上千上万多div,即要让这个类从单例类变成一个普通的可产生多个实例的类。
用代理模式实现单例模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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); }; var ProxySingletonCreateDiv = (function ( ) { var instance; return function (html ) { if (!instance) { instance = new CreateDiv(html); } return instance; } })() var a = new ProxySingletonCreateDiv("a" );var b = new ProxySingletonCreateDiv("b" );console .log(a === b); console .log(a instanceof CreateDiv) console .log(b instanceof ProxySingletonCreateDiv)
我们把管理单例的逻辑移到了ProxySingletonCreateDiv中,这样一来,CreateDiv就变成了普通的类
惰性单例模式 惰性单例指的是在需要时才创建对象实例,本文刚开始的单例模式就是直到调用singleton的时候才创建对象实例。 假设有一个需求是WebQQ的页面中,点击QQ头像时,弹出登陆浮窗,很明显这个浮窗是唯一的。
方案一 在加载页面的时候就创建好这个浮窗,并且这个浮窗在一开始就是隐藏的,只有被点击的时候,才显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <html> <body> <button id="loginBtn" >Login</button> </ body><script> var loginLayer = (function ( ) { 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' ; }; </script> </ html>
这种方式有一个问题,那就是有时候并不想登陆,所以就白白创建了对象,浪费资源,下面改写下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <html> <body> <button id="loginBtn" >Login</button> </ body><script> 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' ; }; </script> </ html>
虽然达到了惰性的效果,但是每次点击都会创建一个对象实例,也很浪费资源,所以接下来用一个变量来标记是否创建了对象实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <html> <body> <button id="loginBtn" >Login</button> </ body><script> 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' ; }; </script> </ html>
通用的惰性单例 上段代码仍然存在以下问题:
违反单一职责原则,创建和管理逻辑都放在createLoginLayer中。
如果下次需要创建唯一的iframe或者script标签,就相当于要把createLoginLayer几乎抄一遍:1 2 3 4 5 6 7 8 9 10 11 12 var createIframe = (function ( ) {var iframe;return function ( ) { if (!iframe) { iframe = document .createElement("iframe" ); iframe.innerHTML = '登录框' ; iframe.style.display = 'none' ; document .body.appendChild(iframe); } return iframe; } })();
所以需要把不变的逻辑抽出来,这个逻辑始终是用一个变量来标志是否创建过对象1 2 3 4 var ojb;if (!obj) { obj = xxx; }
现在我们将管理单例的逻辑抽出来,这些逻辑封装在getsingle函数内部,创建对象的方法fn被当作参数动态传入single函数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var getSingle = function (fn ) { var result; return function ( ) { return result || (result = fn.apply(this , arguements) ); } }; 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: 1 2 3 4 5 6 7 8 9 10 var createSingleIFrame = getSingle(function ( ) { var iframe = document .createElement("div" );; document .body.appendChild(iframe); return iframe; }); document .getElementById('loginBtn' ).onclick = function ( ) { var iframe = createSingleIFrame(); iframe.src = 'www.google.com' ; };
es6中的单例模式 1 2 3 4 5 6 7 8 9 class SingleTon { constructor (name) { if (!SingleTon.instance) { this .name = name; SingleTon.instance = this ; } return SingleTon.instance; } }
用static方法优化下 1 2 3 4 5 6 7 8 9 10 11 12 class SingleTon { constructor (name) { this .name = name; } static getInstance(name) { if (!SingleTon.instance) { SingleTon.instance = new SingleTon(name); } return SingleTon.instance } }
typescript中的单例模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Cache{ public static readonly instance: Cache = new Cache(); private constructor ( ){ } private _data: {[id:string ]: string } = {}; public set (key: string , value: string ) { this ._data[key] = value } public get (key: string ) { return this ._data[key]; } } Cache.instance.set("key" , "value" ); console .log(Cache.instance.get("key" ))