单例模式理解和应用
写在前面:前段时间在看Rx js时候看到观察者模式,联想到单例模式,然后自己学习了下。不看不知道,原来自己在平时写代码的过程中用的最多的就是单例模式。在项目中也有很多应用。这里做下总结。
什么是单例模式?
什么是单例模式?顾名思义,就是只有一个实例。即使多次实例化一个类,也只返回第一次的实例。这样说可能比较抽象,看看实际中最简单的单例模式:
1 2 3 4 5 6
| let hmacsha256 = { name: '哈希加密', encrypt: function() {}, decrypt: function() {} } hmacsha256.name
|
上面字面量形式的创建对象,这个对象hmacsha256有两个方法一个变量。可以通过hmacsha256.encrypt()来调用方法。这是我们最常见的单例模式。但是这样写有个特点,就是hmacsha256的所有方法和变量都是公共的,但是如果有一些内部的辅助函数我们不希望暴露出去的话,这样的单例就无法满足我们的需求。
有私有变量的单例模式
如果像上面说的那样,不想要把所有的方法和变量都暴露出去,以免有的方法被修改,那我们可以只返回自己想要暴露的方法和变量,就像下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var hmacsha256 = function () {
var name = '哈希加密'; function showPrivate() { console.log(name); }
return { getValue: function () { showPrivate(); }, encrypt: function() {}, decrypt: function() {} }; };
var single = hmacsha256(); single.getValue();
|
这样我们对外暴露了3个方法,而name值不在能通过hmacsha256.name
拿到,所以name已经变成了类的私有变量,只有通过single.getValue()
才能拿到,如果,不提供修改这个值的方法,外部就无法修改这个变量。这其实这是一个闭包的典型应用。
通过这个修改以后发我们发现,这个类在应用这个js的时候初始化一次,但是如果这个js里面的方法一直没有被用到的话,那就等于浪费了一些开销,因为一直没有用到。于是我们希望在引入的时候也不实例化,而是在真正使用的时候在实例化,这就引入了一个懒性单例的概念
懒性单例
怎么样能做到引入的时候不实例化呢?我们借助自执行函数来实现。
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 26 27 28 29 30 31
| var hmacsha256 = (function () { var instance; var name = '哈希加密'; function showPrivate() { console.log(name); } function init(option) { return { getValue: function () { showPrivate(); }, encrypt: function() {}, decrypt: function() {} }; }
return { getInstance: function (option) { if (!instance) { instance = init(option); } return instance; } }; })();
hmacsha256.getInstance();
|
自执行函数,我的理解是一种巧妙的方法,使得我们可以将匿名函数以函数表达式的方式进行创建,并返回匿名函数对象的引用。在结尾加上一对括号,可以调用匿名函数对象的引用,让函数立即被执行。
这里需要说明一下,()
的作用,()
的作用是迫使js解析器在解析的时候强制将括号内的表达式(expression)转化为对象,而不是作为语句(statement)来执行 。也就是说(function () {})
这个括号中虽然有function关键字,但是由于有括号,所有解析器并没有把他当做一个function,而是强制把里面的内容转成了一个对象,并返回指向这个对象的指针。
()
在这里的作用与用Eval把json格式字符串转换为json对象 时的作用一样,这就是为啥eval("(" + testJson + ")");
一定要多加一个括号的原因。
1 2
| alert(eval("{}"); alert(eval("({})");
|
事实上,上面的代码和下面的写法的效果一样。
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 26 27 28 29 30 31
| function hmacsha256 () { var instance; var name = '哈希加密'; function showPrivate() { console.log(name); } function init(option) { return { getValue: function () { showPrivate(); }, encrypt: function() {}, decrypt: function() {} }; }
return { getInstance: function (option) { if (!instance) { instance = init(option); } return instance; } }; }
hmacsha256().getInstance();
|
但是为什么要使用自执行函数呢?答案就是隔离作用域 。第二种写法虽然功能可以实现,但是function hmacsha256这个方法随时候有可能被人改写。第一种写法中,就算你hmacsha256返回上千种方法,里面有再多的私有变量,都不影响其他的作用域。他只管hmacsha256这个变量下的东西,就像有一个命名空间一样。
单例模式解决了什么问题?
单例模式只有一个实例,节约了系统的开销。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。比如,工具类,登录框,导航,这些都是系统中单例模式的绝佳使用场景。除了能解决这种业务场景的问题,隔离作用域和模块的分割也是我们使用的最多的姿势。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var hmacsha256 = { encrypt: function () { }, decrypt: function () { } }
var getAuthorization = function (token) { return authorization; }
export { hmacsha256, getAuthorization }
|
单例模式在项目实战应用
项目中有个需求是提供一个sdk,初始化以后可以生成一个顶部和右侧的导航栏。这个导航栏真个项目中只有一个,只需要一个实例,这就是典型的单例模式。
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| window.mySDK = (function () { var instance; function initSDK (option) {
} function showSd (option) { } function resetSd (option) { } function constructor (option) { initSDK(option);
return { head: option.head, side: option.side, sdHide: option.sdHide, showSd: showSd, resetSd: resetSd, init: function () { typeof(option.init)==='function' ? option.init() : undefined }, signOut: function () { typeof(option.signOut)==='function' ? option.signOut() : undefined } } } return { getInstance: function (option) { if (!instance) { instance = constructor(option) } return instance; } } })()
var options = { sdHide: false, resetSd: function () {}, init: function () {} }
mySDK = window.mySDK.getInstance({ head: 'top', side: 'left', resetSd: options.filterSd, sdHide: options.sdHide, init: options.init })
|
这里把实现的具体的内容省去,其实在 initSDK 中做了很多的工作。但这是有关业务的内容,我们需要根据不同的业务员场景自行实现。但运用单例模式,可以保证导航栏只有一个实例。
其他参考文章:
https://zhuanlan.zhihu.com/p/34754447