设计模式-单例模式

# 设计模式-单例模式

单例模式属于创造类型的模式,研究的是对象的创建。

定义:

保证一个类仅有一个实例,并提供一个访问他的全局访问点。

顾名思义,是一个仅需要实例化一次的构造函数。

如果你是进行一个小型的网页开发,多创建一个两个的对象,对性能的消耗根本就是那种无关紧要的对吧,一两个对象才消耗多少性能。这种情况不适合。

如果是一种大型的站点,比方说 wechat,假设现在有一个用户,用户需要登录,并且要保证他在一定事件内登录有效。

你说这种登录功能,你能多次登录吗?这种要求是不是仅需要实例化一次就足以了

假设我们现在要开发一款贪吃蛇的游戏对吧,当你的蛇吃到小点点,会变大。但是,贪吃蛇这个本体,是不是也仅需要在开始游戏的时候实例化一次啊?

接下来我们看看如何创建一个单例模式。

# 单例模式的创建

我们的单例模式的原则是什么?

一个构造函数有且仅能创建一个实例。

假设我们有两个变量 a、b ,我们的 a 通过 new 实例化可以得到一个实例,然后 b 通过同样的操作也能得到一个实例,且 a === b

  • 第一种实现方式

    首先我们回顾一下 new 操作符对构造函数进行了哪些操作? 或者说 new 操作符做了什么事?

    function Person(name){
    	// 创建了一个对象,然后对象继承构造函数的原型,
    	// 改变 this 到这个对象,为对象添加属性。
    	// 实际上是不是下面的这行代码,
    	// this = Object.create(Person.prototype)
    	.
    	.
    	.
    	// 最后 如果没有对象返回就返回 this 
    	// return this
    }
    

    也就是说我们的实现其实只是填充中间那部分对吧? 其他的操作已经通过new 操作符隐式的完成了。

    Ok 那我们来吧!

    我们如果想让一个构造函数返回的是相同的实例。

    是不是最后 return 的 this 必须是相同的,也就是说我们需要一个地方来存放我们首次创建的 this。

    			// 实现方式 1
            function Person(name) {
                if (Person.self) {
                    return Person.self
                }
                Person.self = this;
                this.name = name;
            }
    
            var a = new Person('zhang san');
            var b = new Person('');
            console.log(a === b); // true
    

    但是这种方式不好,为什么说他不好呢?

    他违反了我们设计模式六大原则中的开闭原则。因为他将这个实例完全的暴露出来。

    				var a = new Person('zhang san');
            Person.self = {};
            var b = new Person('');
            console.log(a === b);
    

    如上,我将 Person 的 self 属性重新赋值为一个空对象 {} 那么当你第二次进行实例化的时候,返回的 是 {} 而不是首次实例化的 this

  • 第二种实现方式

    使用闭包的方式,保存一个变量。

            // 实现方式 2
            function Person(name) {
                var self = this;
                this.name = name;
                Person =  function (){
                    return self
                }
            }
    
            var a = new Person('zhang san');
            var b = new Person();
            console.log(a === b);
    

    这种方式其实也不是很好,为什么这么说呢?

    比方说我们除了用到这个实例,还想用到他的原型链。

            // 实现方式 2
            function Person(name) {
                var self = this;
                this.name = name;
                Person = function () {
                    return self
                }
            }
    
            var a = new Person('zhang san');
            Person.prototype.id = '10010';
            var b = new Person();
            console.log(a === b, a.id, b.id); // true undefined undefined
    

    我们在实例化 a 后保存了 一个 ID 到 他的原型链上。但是我们发现 诶 ,在 a,b 上根本取不到这个值。

    这是为什么呢?

    因为我们在首次实例化后,由于 self 保存了 我们的实例,Person 重新赋值 成为了新的函数,也就是 那个匿名函数,然后当我们给 Person 的原型链添加属性的时候,实际上添加的是匿名函数的原型。而不是我们的 原Person函数(构造出实例的那个函数)

    所以这个方案,我们也 pass 掉。

  • 第三种实现方式

    这次我们通过一个立即执行函数来保存实例

            var Person = (function () {
                return ...
            })()
    

    我们 变量 Person 的内容是不是就是 这个立即执行函数返回的内容

            // 实现方式 3
            var Person = (function () {
                var self = null;
                return function (name) {
                    if (self) {
                        return self;
                    }
                    self = this;
                    this.name = name;
                }
            })()
    
            var a = new Person('zhang san');
            Person.prototype.id = '10010';
            var b = new Person();
    
            console.log(a === b, a.id, b.id)
    

    这次,我们创建实例的构造函数保持一致,且变量也持久化。

# 单例模式生成器

我们知道了如何创建单例模式,但是,创建他的话是不是不小的手笔。

所以我们为了解决它,让不会使用,不知道的人也能得到一个单例模式。我们就需要封装一个生成器。

        var getSingle = function (func) {
            var ret = null;
            return function () {
                if (!ret) {
                    ret = func.apply(this, arguments)
                }
                return ret;
            }
        }