04 【JavaScript设计模式】单例模式

写在前面

这个系列的文章是通过对《JavaScript设计模式》一书的学习后总结而来,刚开始觉得学习的时候只需看书即可,不用再另外记录笔记了,但是后面发现书中有些内容理解起来并不是很容易,所以结合书中的描述就将自己的理解也梳理了一下并将它记录下来,希望和大家一起学习,文章中如果有我理解错的内容,请各位批评指正,大家共同进步~

从这篇文章我们开始真正学习JavaScript的设计模式,首先是学习第一个设计模式——单例模式。

单例模式含义

单例模式其实在JS中是一个最简单、也是最常见的设计模式,它的含义也比较简单:保证一个类仅仅有一个实例,并且只提供一个访问它的全局访问点。这句话不好理解的话,给大家再举个栗子:我们使用CDN的方式在项目代码里引入的JS库文件,其实它的入口就是一个单例,比如我们的jQuery。当我们在页面中通过<script>标签引入jQuery后,在JS代码里就可以使用"$"符号来访问jQuery里的所有方法了,这里的$符号其实就是jQuery对外提供的唯一访问点,也是jQuery的唯一一个对象实例,我们可以这么理解。

单例模式应用场景

单例模式的应用场景其实非常多,比如有些对象我们只需要一个的话,那么就可以使用单例模式,比如线程池、全局缓存、浏览器的window对象等,还有我们前后端交互的时候所用的数据库连接、webSocket连接等都可以使用单例,还有一些前端UI组件也可以用单例,比如那些弹窗,最简单的就是登录弹窗。用户如果登陆了就不给他显示登录弹窗,如果没登录的话就要在用户点击登录按钮后显示弹窗,显示弹窗的时候我们不可能给他每次点击登录按钮时都创建一个弹窗吧,只能是首先创建一个弹窗,在用户点击了这个按钮时将它显示,这样一来就能确保你的整个应用中是只有一个登陆弹窗的,而不是每次点击登录按钮时给他创建多个弹窗造成不必要的DOM浪费。

单例模式实现思想

就像上述应用场景提到的一样,每次点击按钮时仅仅控制弹窗显示与否即可,并不需要每次都创建一个新的弹窗。那这样的需求如何实现呢,其实很简单,我们只需要定义一个变量即可,这个变量用来标记弹窗或者某个单例是否已经创建,如果创建了则返回创建后的对象,如果没创建就新建一个对象即可。所以单例模式的实现仅需要一个变量就可以完成。

单例模式具体实现

一、正常的实现

此处我们不讨论传统语言中单例模式的实现过程,因为JS本身就是一门没有“类”的编程语言,在ES6开始虽然加入了类的概念,但那更多的是一种语法糖。所以在JS中实现单例模式跟在传统编程语言中的实现过程不太一致,我们也没有必要将传统语言中单例模式的实现过程生搬硬套在JS中。在JS中实现单例模式可以说是更加的简单。

单例模式的核心概念其实只有两点:保证只有一个对象,并且提供全局访问。

那么在JS中我们通过对象字面量的方式创建的对象,就能保证“只有一个对象”这个条件,其次,我们将这个对象定义在全局作用域中,就能满足“提供全局访问”这个条件,所以结合来看,通过对象字面量的方式,定义在全局作用域中的对象完完全全满足单例模式的两个条件,所以单例模式在JS中最简单的实现方式如下:

var objectA = {};    //定义在全局作用域中

但是以上这种方式虽然实现了单例模式,却存在一个问题,全局作用域中定义对象会造成作用域环境污染,并且在代码后期某个地方很有可能会出现不经意间将这个对象修改掉的情况。这两个问题,我们通过以下两种方式来解决:

1、使用命名空间

使用命名空间其实并不会解决全局作用域污染这个问题,但是在一定程度上可以减少全局作用域中变量的数量,如下方式:

        var nameSpaceA = {
            objectA: function() {

            },
            objectB: function() {

            }
        };

上述代码中我们将objectA和objectB直接定义成了nameSpaceA的属性,这就减少了全局作用域中变量的数量,在一定程度上规避了变量直接跟全局作用域打交道的机会。

2、使用闭包封装私有变量

除了命名空间的方式之外,我们可以使用闭包直接将一些变量封装为私有变量,如下:

        var objectB = (function() {
            var _name = 'www.xbeichen.cn';
            var _age = 25;
            return {
                getInfo: function() {
                    return `${_name}的年龄已经是${_age}岁了哦`;
                }
            }
        })();

上述代码中我们将_name和_age两个变量用闭包进行了封装,这样一来在全局作用域中是访问不到这两个变量的。

二、惰性单例

惰性单例是单例模式中的一个重点,它的含义很简单:在需要的时候才创建对象,并不是一开始就创建对象。

还是用登录窗的例子来说事,如果不用惰性单例的话,可能是这样的实现方式:当我们进入系统的时候,这个登录弹窗也会随之创建,不过此时它是隐藏的,当用户点击登录按钮的时候它的显示属性才会改变,它才会弹出登录面板让用户登录。但是这样存在的一个问题就是,如果用户打开网页就是想看看网页上的其他信息,并不想登录的话,是不是就白白创建了一个登录弹窗,但却没有用到它呢,这样造成了DOM浪费啊。

如果用了惰性单例,那可能是这样的情形:当我们进入系统的时候,不会一开始就创建这个登录弹窗,只有在用户点击了登录按钮的时候,才会去创建并让它显示,这样一来就避免了多余的DOM浪费。但是这样一来又有问题,这样仅仅是实现了惰性的特点,并没有单例的效果,因为每次点击的时候都会创建这个弹窗,这个问题解决起来其实很简单,我们定义一个变量来标记是否创建过弹窗就可以啦,这样就达到了惰性单例的效果,如下:

        //惰性单例
        var createLoginView = (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('loginButton').onclick = function() {
            var loginView = createLoginView();
            loginView.style.display = 'block';
        }

上述代码我们实现了惰性单例,但是这段代码违反单一职责原则,所有的逻辑都在createLoginView对象内部,假如后期我们想创建iframe、script这些标签的时候,我们就需要去修改createLoginView内部的代码,还得照抄一遍,这样是很糟糕的一件事情。

我们按下面的方法实现一个通用的惰性单例:

        //通用的惰性单例
        var getSingle = function(fn) {
            var result;
            return function() {
                return result || (result = fn.apply(this,arguments));
            }
        };

        var createLoginView = function() {   //创建div
            var div = document.createElement('div');
            div.innerHTML = '登录弹窗';
            div.style.display = 'none';
            document.body.appendChild(div);
            return div;
        };
        var createSingleLoginView = getSingle(createLoginView);

        var createIframeView = function() {   //创建iframe
            var iframe = document.createElement('iframe');
            document.body.appendChild(iframe);
            return iframe;
        };
        var createSingleIframeView = getSingle(createIframeView);

        document.getElementById('loginButton').onclick = function() {
            var loginView = createSingleLoginView();
            loginView.style.display = 'block';
        }

        document.getElementById('iframeButton').onclick = function() {
            var iframeView = createSingleIframeView();
            iframeView.src = 'http://www.xbeichen.cn';
        }

在上面的代码里我们将创建标签对象的代码和管理单例的代码分别放在了不同的方法中,这两个方法互不影响,但是当它们组合使用时却完成了单例模式的实现,这无疑是很成功的。

总结

这篇文章我们介绍了一下最简单的单例模式在JS中的实现,介绍的时候并没有像传统语言中那样去生搬硬套,而是充分使用了JS语言的特点来实现的,包括后面实现惰性单例的时候,用到了JS中的高阶函数、闭包这些知识点,所以大家在学习设计模式之前还是要有一定的JS高级基础知识才可以。

猜你喜欢

转载自blog.csdn.net/qq_35117024/article/details/105816702