第四章-单例模式
cooljser 2020-06-08  
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。例如:线程池、全局缓存、浏览器中的 window 对象等。实际场景中,比如要实现一个登陆弹窗,这个弹窗只会被创建一次,那么就可以使用单例模式来生成。
# 实现单例模式
最基础的版本,就是使用一个变量来标识当前对象是否已创建,若已创建则直接返回之前创建的对象,没有创建则创建后返回,示例代码如下:
var Singleton = function (name) {
    this.name = name;
    this.instance = null;
};
Singleton.prototype.getName = function () {
    return this.name;
};
Singleton.getInstance = function (name) {
    if (!this.instance) {
        this.instance = new Singleton(name);
    }
    return this.instance;
};
var a = Singleton.getInstance('jack');
var b = Singleton.getInstance('jack');
console.log(a === b); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
上面这个示例中的 Singleton 类是"不透明"的,因为调用者必须知道这是个单例类,并且使用 Singleton.getInstance 才能获取到单例对象。
# 透明的单例模式
现在我们来实现一个"透明"的单例类,让使用者可以像使用其他类一样,从这个类中创建对象,示例代码如下:
var CreateDiv = (function () {
    var instance;
    var CreateDiv = function (html) {
        if (instance) {
            return instance;
        }
        this.html = html;
        this.init();
        return (instance = this);
    };
    CreateDiv.prototype.init = function () {
        var div = document.createElement('div');
        div.innerHTML = this.html;
        document.body.append(div);
    };
    return CreateDiv;
})();
var a = new CreateDiv('jack');
var b = new CreateDiv('jack');
console.log(a === b); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
虽然看起来完成了一个"透明"的单例类,但是它也有一些缺点。为了把 instance 封装起来,我们使用了一个自执行的匿名函数和闭包,并且这个匿名函数返回了真正的 Singleton 构造方法,这无疑增加了程序的复杂度,也让代码阅读起来很困难。
另外 CreateDiv 的构造函数也违背了"单一职责"原则,这个函数做了两件事情,一是创建对象,二是执行初始化函数。
# 用代理实现单例模式
现在我们通过引入代理类的方式,来解决上面的问题。
 var CreateDiv = function (html) {
     this.html = html;
     this.init();
 };
CreateDiv.prototype.init = function () {
    var div = document.createElement('div');
    div.innerHTML = this.html;
    document.body.append(div);
};
var ProxySingletonCreateDiv = (function () {
    var instance;
    return function (html) {
        if (!instance) {
            instance = new CreateDiv(html);
        }
        return instance;
    };
})();
var a = new ProxySingletonCreateDiv('jack');
var b = new ProxySingletonCreateDiv('jack');
console.log(a === b); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上述代码中,通过引入代理类的方式,实现了一个单例模式的编写,跟之前不一样的是,现在我们把负责管理单例的逻辑迁移到了代理类 ProxySingletonCreateDiv 中。这样一来,CreteDiv 就变成了一个普通的类,它跟 ProxySingletonCreateDiv 组合起来就可以达到单例模式的效果。
# 通用的惰性单例
惰性单例指的是在需要的时候才创建对象单例,如下示例:
var getSingle = function (fn) {
     var result;
     return function () {
         return result || (result = fn.apply(this, arguments));
     };
 };
var createLoginLayer = function () {
    var div = document.createElement('div');
    div.innerHTML = '我是登录浮窗';
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
};
var createSingleLoginLayer = getSingle(createLoginLayer);
// 如果要创建唯一的 ifram,可以这么写
var createSingleIframe = getSingle(function () {
    var iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    return iframe;
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这种单例模式的用途不至于创建对象,也可以作用于任何的其他函数,比如绑定事件等等。
