1、单例模式
单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
线程池、全局缓存、浏览器中的window对象
登录浮窗
- 当我们单击登录按钮的时候,页面中会出现一个登录浮窗,
- 而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,
- 那么这个登录浮窗就适合用单例模式来创建。
code
- test1-1/test1-2
- 不足(不透明)
- 我们通过Singleton.getInstance来获取Singleton类的唯一对象,这种方式相对简单,
- 但有一个问题,就是增加了这个类的
“不透明性”
, - Singleton类的使用者必须知道这是一个单例类,跟以往通过new XXX的方式来获取对象不同,
- 这里偏要使用Singleton.getInstance
var a = Singleton.getInstance("sven1");
来获取对象。
- 本质
- 是访问的同一变量,
- 第一次访问设置值,
- 再次访问返回上一次设置的值
- 不足(不透明)
- test2
- 优点:完成了一个透明的单例类的编写,但它同样有一些缺点。
- 缺点:
- 1、为了把instance封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的Singleton构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服。
- 2、CreateDiv的构造函数实际上负责了两件事情。第一是创建对象和执行初始化init方法,第二是保证只有一个对象
- 3、假设我们某天需要利用这个类,在页面中创建千千万万的div,即要让这个类从单例类变成一个普通的可产生多个实例的类,把控制创建唯一对象的那一段去掉
if (instance) {return instance;}
,这种修改会给我们带来不必要的烦恼。jsvar CreateDiv = (function () { var instance; 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; })();
- test3
- 优点
- 引入代理
- 把负责管理单例的逻辑移到了代理类proxySingletonCreateDiv中。
- CreateDiv就变成了一个普通的类,
- 它跟proxySingletonCreateDiv组合起来可以达到单例模式的效果。
- 优点
- test4
- 重新分析:
- 1、不适用
- test3的做法:在传统面向对象语言,很自然的做法
- JavaScript其实是一门无类(class-free)语言,生搬单例模式的概念并无意义
- 在JavaScript中创建对象的方法非常简单,既然我们只需要一个
“唯一”
的对象,为什么要为它先创建一个“类”呢? - 这无异于穿棉衣洗澡,传统的单例模式实现在JavaScript中并不适用
- 但是如果使用
es6
开发还是适用的
- 2、本质/核心
- 单例模式的核心是确保只有一个实例,并提供全局访问
全局变量
- 似乎是个不错的选择
- 全局变量存在很多问题,它很容易造成命名空间污染
- 其他选择
- 使用
命名空间
- 适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。
- 最简单的方法依然是用对象字面量的方式:
- 动态地创建命名空间,即动态的创建对象属性
- 使用
闭包封装私有变量
- 这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:
- 使用
- 1、不适用
- 重新分析:
- test5
- 惰性单例
- 惰性单例指的是在需要的时候才创建对象实例
- test5-1 第一种解决方案
- 思路
- 是在页面加载完成的时候便创建好这个div浮窗,
- 这个浮窗一开始肯定是隐藏状态的,
- 当用户点击登录按钮的时候,它才开始显示:
- 不足
- 也许我们进入WebQQ只是玩玩游戏或者看看天气,根本不需要进行登录操作,
- 因为登录浮窗总是一开始就被创建好,那么很有可能将白白浪费一些DOM节点。
- 思路
- test5-2 第二种解决方案
- 不足
- 现在达到了惰性的目的,但失去了单例的效果
- 当我们每次点击登录按钮的时候,都会创建一个新的登录浮窗div。
- 虽然我们可以在点击浮窗上的关闭按钮时(此处未实现)把这个浮窗从页面中删除掉,
- 但这样频繁地创建和删除节点明显是不合理的,也是不必要的。
- 不足
- test5-3 第三种解决方案
- 完成了一个可用的惰性单例
- 不足
- 这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在createLoginLayer对象内部
- 如果下次需要创建页面中唯一的iframe,或者script标签,用来跨域请求数据,就必须得如法炮制,把createLoginLayer函数几乎照抄一遍:
- test5-4 (最终)
- 1、把不变的部分隔离出来
- 先不考虑创建一个div和创建一个iframe有多少差异
- 管理单例的逻辑其实是完全可以抽象出来的,这个逻辑始终是一样的
- 用一个变量来标志是否创建过对象,
- 如果是,则在下次直接返回这个已经创建好的对象:
jsvar obj; if ( ! obj ){ obj = xxx; }
- 2、成品
- 把如何管理单例的逻辑从原来的代码中抽离出来,这些逻辑被封装在getSingle函数内部,创建对象的方法fn被当成参数动态传入getSingle函数
jsvar getSingle = function( fn ){ var result; return function(){ return result || ( result = fn .apply(this, arguments ) ); } };
- 将用于创建登录浮窗的方法用参数fn的形式传入getSingle,我们不仅可以传入createLoginLayer,还能传入createScript、createIframe、createXhr等。
- 之后再让getSingle返回一个新的函数,并且用一个变量result来保存fn的计算结果。
- result变量因为身在闭包中,它永远不会被销毁。
- 在将来的请求中,如果result已经被赋值,那么它将返回这个值
jsvar 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'; };
- 3、最终
- 我们把创建实例对象的职责和管理单例的职责分别放置在两个方法里,
- 这两个方法可以独立变化而互不影响,
- 当它们连接在一起的时候,就完成了创建唯一实例对象的功能,看起来是一件挺奇妙的事情。
- 1、把不变的部分隔离出来
- test5-5 (最终)
- 这种单例模式的用途远不止创建对象,
- jQuery选择给节点绑定one事件:
- 模拟one实现只绑定一次事件
- 惰性单例
- test1-1/test1-2