JavaSript设计模式-单例模式

不是前言的前言

说实话,写文章好麻烦啊,看书,查资料,反复确认,自己学会了,理解了原理,还要用语言,文字,例子去描述出来,保证大部分人看懂。

但是!有人说但是之前的话毫无意义,我想还是有意义的。因为在写文章的过程中,我又升华了,又深刻了,又接受不完美的我了。

 

前言

这次我将开始围绕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举例),那么下面这段话可以帮助你更好的理解:

静态方法在程序开始时生成内存,实例方法在程序运行中生成内存,
所以静态方法可以直接调用,实例方法要先成生实例,通过实例调用方法,静态速度很快,但是多了会占内存。

 

单例模式的应用场景

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

  • 全局缓存等

 

总结

首先说的还是关于内存分配的问题,如果不了解,或者感觉很吃力的话,可以暂时先放下内存问题不必钻牛角尖,当学习到一定程度时,自然会迎刃而解的。

其次是单例模式只是比较简单的设计模式,而我写了很多废话,扣字眼的去逐一解释,不是为了显示我是如何的博学与聪明,而是我学习过程中实实在在遇到的问题与疑惑,其实网上好多文章就是贴个代码,写个注释,知道怎么写单例就行了,而我学习时,学会写很快,一分钟吧,复制一下,示例代码,输出一下结果,看下结构就明白了,但感觉总是很模糊不清,不知其所以然,希望这篇文章能对同样在学习的小伙伴们有所帮助。

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

 

参考资料:

 

 

《JavaScript设计模式》——9.4 Singleton(单例)模式  

依然再有人吐槽徐涛的翻译...下面是英文原版

《JavaScript设计模式》 [美]Addy Osmani 开源英文原版

 

 

 

 

 

转载于:https://my.oschina.net/swmhxhs/blog/3051013

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值