JavaSript设计模式-单例模式

124 阅读7分钟

封面动物

大脑腐~

不是前言的前言

说实话,写文章好麻烦啊,看书,查资料,反复确认,自己学会了,理解了原理,还要用语言,文字,例子去描述出来,保证大部分人看懂。
但是!有人说但是之前的话毫无意义,我想还是有意义的。因为在写文章的过程中,我又升华了,又深刻了,又接受不完美的我了。

前言

这次我将开始围绕javascript设计模式开始学习、写作。
本文是我学习过程的总结归纳,与输出学习结果的尝试,尽量写的细致一些,直白些,硬性指标和软性解释都尽量写出来,保证新手小白们都能看懂,学会(我能学明白,大部分人应该都能学明白~love&peace)。
阅读本文之前请达成以下先决条件:
  1. 熟悉,掌握JavaScript关于类的知识点
  2. 熟悉,掌握,在实操代码的帮助下能够理解闭包
  3. 了解什么是模式,推荐阅读本人之前文章,对模式有个初步印象。


单例(Singleton)模式

单例模式又称单体模式,它的本质作用是:限制类只能实例化一次。
实现的过程概括为一句话就是:如果已存在实例,则返回其引用,如果不存在,则创建当前类的实例并返回(如果你已有女票,则直接嘿嘿嘿,如果你没有,则找个女票,然后嘿嘿嘿)。
优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如弹窗),避免对资源的多重占用。
缺点:没有接口,不能继承。

字面量对象单例

首先啰嗦一下,我本来不打算写字面量对象单例的,感觉这个没啥用,因为太常见了,我刚见这个情形时我都在怀疑这是单例吗?
但后来发现不得不写,因为不写这段,我感觉后面的有关惰性单例(延迟执行)没办法解释清楚。
实现代码:
var singleton = {
        attr : 1,
        method : function(){ return this.attr; }
    }
var t1 = singleton ;
var t2 = singleton ;

console.log(t1===t2);//true
console.log(t2.method());//1
至此,因为 t1 和t2都引用的是同一个singleton对象,内存中指针都是指的一个地址,所以相等。
而对于方法 method,singleton充当共享资源命名空间,从全局命名空间中隔离出代码实现,从而为函数提供单一访问点。
所以singleton 就是平时很常见的字面量对象,你可以把 singleton 看做已初始化的实例对象,或者把singleton 换一种形式可能更容易帮助你理解:
var singleton = new Object({
    attr: 1,
    method: function () { return this.attr; }
});

var t1 = singleton;
var t2 = singleton;

console.log(t1 === t2);//true
console.log(t2.method());//1
你会发现,singleton 其实就是Object构造函数的实例化对象,只不过字面对象简化了操作而已,而且它符合单例的本质:限制类只能实例化一次。它确实只实例化了一次。
如果你再new一个具有相同的属性和方法的实例对象,那么是不是再次实例化了呢?答案是:false;
var singleton = new Object({
    attr: 1,
    method: function () { return this.attr; }
});

var other = new Object({
    attr: 1,
    method: function () { return this.attr; }
});

var t1 = singleton;
var t2 = other;

console.log(t1 === t2);//false
为什么?
虽然是具有相同的属性和方法的实例对象,但是本质上,是不相同的,因为分配进内存中的地址不相同,单例模式真正的实质每次调用构造函数时,返回指向同一个对象的指针,显然上述的的方式不符合该要求。(关于内存中地址的分配问题,如果你没相关的知识背景或者没有了解过,不用过分纠结。暂时可理解为:同名同姓的两个人【具有相同的属性和方法的实例对象】,但现实生活中并不是一个人【内存中的地址不相同】。)

通过闭包构建的防篡改单例

示列中的代码我是直接复制《JavaScript设计模式》的代码,在最后的参考资料中我会附上地址。
实现代码:
var mySingleton = (function () {
    // 实例保持了Singleton的一个引用
    var instance;
    function init() {
        // Singleton
        // 私有方法和变量
        function privateMethod() {
            console.log("I am private");
        }
        var privateVariable = "Im also private";
        var privateRandomNumber = Math.random();

        return {
            // 公有方法和变量--真正可以被实例访问使用的方法和变量
            publicMethod: function () {
                console.log("The public can see me!");
            },
            publicProperty: "I am also public",
            getRandomNumber: function () {//公有方法返回了私有变量--对外的接口
                return privateRandomNumber;
            }
        };
    };
    //立即执行函数返回 getInstance接口,通过闭包保持着 instance的引用。
    return {
        // 获取Singleton的实例,如果存在就返回,不存在就创建新实例
        getInstance: function () {
            if (!instance) {
                instance = init();
            }
            return instance;
        }
    };
})();

var t1 = mySingleton.getInstance();
var t2 = mySingleton.getInstance();

console.log(t1 === t2);//true
单例模式的标准实现代码如上,其中有以下几个知识点:
  1. 立即执行函数返回init类对外唯一实例化接口getInstance,没有其他方式获取实例。
  2. 通过闭包,保持着instance对inti类实例的唯一引用,且不可修改。
  3. getInstance函数达成了单例模式要求(限制类只能实例化一次,如果已存在实例,则返回其引用,如果不存在,则创建当前类的实例并返回)
  4. 延迟创建(执行)

为何延迟执行对于Singleton很重要?

这句话是是原书中的原句问题,看了书中的的解释很模糊(因为我没有C++编程经验,也没能理解它并不能伸缩是什么意思。你看,我并不聪明。)原文如下:
在C++中,Singleton负责隔绝动态初始化顺序的不可预知性,将控制权归还给程序员。 值得注意的是类的静态实例(对象)和Singleton之间的区别:当Singleton可以作为一个静态的实例实现时,它也可以延迟构建,直到需要使用静态实例时,无需使用资源或内存。 如果我们有一个可以直接被初始化的静态对象,需要确保执行代码的顺序总是相同的(例如:在初始化期间objCar需要objWheel的情况),当我们有大量的源文件时,它并不能伸缩。
但是,我在本文开始时举了关于字面量对象单例的栗纸~可以帮助理解一下为何延迟执行对于Singleton很重要。
字面量对象-书写即创建执行。JS执行逻辑是逐行执行,遇到字面量对象,即刻创建,即刻分配内存地址。而单例延迟执行,则是按需调用(很像函数不是吗?),避免了不必要的浪费。
很诚恳的说,其实是上述强行解释了一波,实际上文所指的内容,如静态类,其实JS中并不存在。但是,模拟一下还是可以滴:
var StaticClass = function(){};
StaticClass.staicVariable = "staicVariable";
StaticClass.staicMethod = function(){
    console.log('模拟一下');
};

console.log(StaticClass.name);      //'staicVariable'
StaticClass.staicMethod(1,3);  //'模拟一下'
如果你有了解,学习过是PHP的话(我只学过PHP,所以用PHP举例),那么下面这段话可以帮助你更好的理解:
静态方法在程序开始时生成内存,实例方法在程序运行中生成内存, 所以静态方法可以直接调用,实例方法要先成生实例,通过实例调用方法,静态速度很快,但是多了会占内存。

单例模式的应用场景

  • 全局唯一模态框/对话框/弹窗
  • 全局缓存等

总结

首先说的还是关于内存分配的问题,如果不了解,或者感觉很吃力的话,可以暂时先放下内存问题不必钻牛角尖,当学习到一定程度时,自然会迎刃而解的。
其次是单例模式只是比较简单的设计模式,而我写了很多废话,扣字眼的去逐一解释,不是为了显示我是如何的博学与聪明,而是我学习过程中实实在在遇到的问题与疑惑,其实网上好多文章就是贴个代码,写个注释,知道怎么写单例就行了,而我学习时,学会写很快,一分钟吧,复制一下,示例代码,输出一下结果,看下结构就明白了,但感觉总是很模糊不清,不知其所以然。
希望这篇文章能够帮助小伙伴们在掀开模糊的面纱之路上,更进一步。

最后,共同学习进步,别放弃,想想你爱的人和爱你的人。