随着项目做的越来越多,项目越来越大,也越来越意识到设计模式的重要性,好的设计模式可以大幅简化项目的复杂度和耦合性,使编写、维护都变得轻松许多。
单例模式的定义是:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏 览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少 次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
——《Javascript设计模式与开发实践》
书上都讲的很清楚了,但是,只是看一遍照着练一遍是没用的,把书合上,自己从头实现一遍,自己把该踩的坑再踩一遍,才能真正的学会。如果能讲清楚,那就真是学明白了。而且,人和人的思维方式是不一样的,所以就可能有人看书看不懂,结果看我的博客却看懂了,那便真是极好的~
正题
单例模式的核心是确保只有一个实例,并提供全局访问。
所以说,全局变量本身就是一个单例。但是,这样会污染全局作用域,不过这个理由并不充分,因为要全局访问必须要存在于全局作用域中啊~但还有一个问题,就是全局变量易被覆盖,所有我们就需要一个能随时创建出同一个对象的函数。
实现单例模式,简单来说,就是用一个变量来标记一下之前是否创建过要获取的对象,如果没有创建过,就创建一个新对象,并且把这个对象保存起来。否则,就直接返回那个对象。
可以将那个变量的初始值设为 null
,然后将新创建的对象赋给它,这样判断它的真假就可以知道之前是否创建过对象,如果创建过,就直接返回它。既可以起到标记作用,也可以起到保存作用。
但是用一个全局变量来作为标记太不优雅,还会污染全局作用域,还不符合封装原则,并且,还会出现跟开头提到的同样的问题,这个时候就要请出闭包了~
工厂模式
var CreateObj = (function () { // 我们把这个匿名函数称作 oldFn
let obj = null;
return () => { // 我们把这个匿名函数称作 newFn
if (obj) {
return obj
}
obj = {
text: "嘿嘿"
}
return obj
}
})()
var o1 = CreateObj()
var o2 = CreateObj()
console.log(o1 === o2);
// true
为了叙述方便,我在注释中为匿名函数命了名
通过立即执行函数表达式,CreateObj
变成了等号后面的那个函数的返回值——一个匿名函数 newFn
,而这个函数会保存 oldFn
中的作用域,也就是说它会保存 obj
这个对象,这样每次调用时访问的就都是同一个 obj
,于是,我们就可以通过这种方式使得每次调用 CreateObj()
获得的都是同一个对象。
上面的代码的 newFn
中也可以简写成如下形式
return obj || (obj = {text: "嘿嘿" })
构造函数模式
var Person = (function () {
let obj = null
return function () {
if(obj) {
return obj;
}
this.name = "老司机~��";
obj = this;
}
})()
var p1 = new Person()
var p2 = new Person()
console.log(p1);
// { name: '老司机~��' }
console.log(p1 === p2);
// true
构造函数模式和工厂模式没有什么区别,只是创建新对象的方式不同而已。
但是,还有一个问题,即这样不管是想把一个构造函数/工厂函数改为单例模式,还是改回来,都很复杂,能不能用一个通用的函数来处理呢?
通用的单例模式
// 单例化之前的工厂函数
function createObj(name) {
return {
name: name
}
}
var getSingleFun = function (fn) {
var obj = null;
return function () {
return obj || (obj = fn.apply(this, arguments));
}
}
var createSingleObj = getSingleFun(createObj);
var o1 = createSingleObj('小明')
var o2 = createSingleObj('小马')
console.log(o1 === o2);
// true
console.log(o1.name);
// 小明
console.log(o2.name);
// 小明
这个方法会比之前的更通用,向 getSingleFun
中传入任意函数,都可以返回一个新的函数,通过这个新的函数即可创建单例。
但是这会有一个问题,即只有第一次创建时的参数有效,之后创建时的参数将不起作用,但这需要看具体的问题,如果要求全局只有唯一一个目标对象,那么这样是没问题的。但如果像 Angular 一样,参数相同时返回同一个对象,参数不同时返回不同的对象,就需要在 getSingleFun
中创建新对象时记录下它的参数,再创建时检测是否有参数相同的实例,若有则直接返回。这里就先不写了。