设计模式 -- 单例模式(传统面向对象与JavaScript 的对比实现)

单例模式 -- 传统面向对象与JavaScript 的对比实现

文章目录

传统的面向对象的实现

定义

单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

实现思路

用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

初级实现

javascript 复制代码
		var Singleton = function (name) {
            this.name = name; this.instance = null;
        };
        Singleton.prototype.getName = function () {
            alert(this.name);
        };
        Singleton.getInstance = function (name) {
            if (!this.instance) {
                this.instance = new Singleton(name);
            }
            return this.instance;
        };
        const a = Singleton.getInstance('sven1'); 
        const b = Singleton.getInstance('sven2');
        alert(a === b); // true

缺点

类的不透明性

通过 Singleton.getInstance 来获取 Singleton 类的唯一对象,这种方式相对简单,但有 一个问题,就是增加了这个类的"不透明性",Singleton 类的使用者必须知道这是一个单例类, 跟以往通过 new XXX 的方式来获取对象不同,这里偏要使用 Singleton.getInstance 来获取对象。

透明的单例模式实现

目的(实现效果)

我们现在的目标是实现一个"透明"的单例类,用户从这个类中创建对象的时候,可以像使 用其他任何普通类一样。

实现

javascript 复制代码
		const Singleton = (function (name) {
            let instance;

            function createDiv(name) {
                if (instance) {
                    return instance;
                }
                this.name = name;
                this.init();
                return instance = this;
            }

            createDiv.prototype.init = function () {
                const div = document.createElement('div');
                div.innerHTML = this.name;
                document.body.appendChild(div);
            }

            return createDiv;
        })();


        const a = new Singleton('sven1');
        const b = new Singleton('sven2');

缺点

  1. 为了把 instance 封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的 Singleton 构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服。
  2. 不符合单一职责,不利于维护。构造函数实际上负责了两件事情。第一是创建对象和执行初始化 init 方法,第二是保证只有一个对象。

用代理实现单例模式

javascript 复制代码
		const CreateDiv = function (name) {
            this.name = name;
            this.init();
        }

        CreateDiv.prototype.init = function () {
            const div = document.createElement('div');
            div.innerHTML = this.name;
            document.body.appendChild(div);
        };


        const ProxySingletonCreateDiv = (function () {
            let instance;
            return function (name) {
                if (!instance) {
                    instance = new CreateDiv(name);
                }
                return instance;
            }
        })();

        const a = new ProxySingletonCreateDiv('seven1');
        const b = new ProxySingletonCreateDiv('seven2');

        alert(a === b);

优点

跟之前不同的是,现在我们把负责管理单例的逻辑移到了代理类 proxySingletonCreateDiv 中。这样一来,CreateDiv 就变成了 一个普通的类,它跟 proxySingletonCreateDiv 组合起来可以达到单例模式的效果。

JavaScript 中的单例模式实现

以上提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从 "类"中创建而来。在以类为中心的语言中,这是很自然的做法。比如在 Java 中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来的。

但 JavaScript 其实是一门无类(class-free)语言

单例模式的核心是确保只有一个实例,并提供全局访问。

实现一:全局变量(不推荐)

如果 a 变量被声明在全局作用域下, 则我们可以在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所当然的。这样就 满足了单例模式的两个条件。

缺点

全局变量存在很多问题,它很容易造成命名空间污染

实现二:惰性单例

惰性单例指的是在需要的时候才创建对象实例

如 上面初级实现中 instance 实例对象总是在我们调用 Singleton.getInstance 的时候才被创建,而不是在页面加载好 的时候就创建

实现要点

  1. 使用惰性单例:需要的时候才创建
  2. 把不变的部分隔离出来。先不考虑创建一个 div 和创建一个 iframe 有多少差异,抽离管理单例的逻辑,这个逻辑始终是一样的: 用一个变量来标志是否创建过对象,如果是,则在下次直接返回这个已经创建好的对象:

JavaScript 的 最终实现

javascript 复制代码
		var getSingle = function (fn) {
            var result;
            return function () {
                return result || (result = fn.apply(this, arguments));
            }
        };

        var createSingleIframe = getSingle(function () {
            var iframe = document.createElement('iframe');
            document.body.appendChild(iframe);
            return iframe; 6
        });

        document.getElementById('loginBtn').onclick = function () {
            var loginLayer = createSingleIframe();
            loginLayer.src = 'http://baidu.com';
        };

把创建实例对象的职责和管理单例的职责分别放置在两个方法里,这两 个方法可以独立变化而互不影响,当它们连接在一起的时候,就完成了创建唯一实例对象的功能。

应用场景

  • 线程池、全局缓存、浏览器中的 window 对象等
  • 创建单一实例

参考文献:

JavaScript 设计模式与开发实践 (by 曾探)

相关推荐
lichong9512 小时前
【Flutter&Dart】 listView.builder例子二(14 /100)
android·javascript·flutter·api·postman·postapi·foxapi
AH_HH2 小时前
如何学习Vue设计模式
vue.js·学习·设计模式
破浪前行·吴3 小时前
【初体验】【学习】Web Component
前端·javascript·css·学习·html
難釋懷4 小时前
命令模式详解与应用
设计模式·命令模式
染指悲剧5 小时前
vue实现虚拟列表滚动
前端·javascript·vue.js
浩浩测试一下6 小时前
Web渗透测试之XSS跨站脚本之JS输出 以及 什么是闭合标签 一篇文章给你说明白
前端·javascript·安全·web安全·网络安全·html·系统安全
angen20186 小时前
二十三种设计模式-原型模式
设计模式·原型模式
前端搬运工X7 小时前
Object.keys 的原生 JS 类型之困
javascript·typescript
肖老师xy8 小时前
h5使用better scroll实现左右列表联动
前端·javascript·html
一路向北North8 小时前
关于easyui select多选下拉框重置后多余显示了逗号
前端·javascript·easyui