JS 设计模式之单例模式

来源:互联网 发布:淘宝家装干线物流模板 编辑:程序博客网 时间:2024/05/19 17:27

随着项目做的越来越多,项目越来越大,也越来越意识到设计模式的重要性,好的设计模式可以大幅简化项目的复杂度和耦合性,使编写、维护都变得轻松许多。

单例模式的定义是:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏 览器中的 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);//  trueconsole.log(o1.name);//  小明console.log(o2.name);//  小明

这个方法会比之前的更通用,向 getSingleFun 中传入任意函数,都可以返回一个新的函数,通过这个新的函数即可创建单例。

但是这会有一个问题,即只有第一次创建时的参数有效,之后创建时的参数将不起作用,但这需要看具体的问题,如果要求全局只有唯一一个目标对象,那么这样是没问题的。但如果像 Angular 一样,参数相同时返回同一个对象,参数不同时返回不同的对象,就需要在 getSingleFun 中创建新对象时记录下它的参数,再创建时检测是否有参数相同的实例,若有则直接返回。这里就先不写了。

原创粉丝点击