单例模式

单例模式

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点
注意:在单线程的js环境里里不存在线程安全的问题

实现单例模式

实现单例模式并不复杂,只不过是用一个变量来标志当前是否已经为某个类型创建过对象,如果是,则下一次获取该类的实例时,直接返回之前创建的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Singleton = function(name) {
this.name = name;
}

Singleton.instance = null;
Singleton.prototype.getName = function() {
return this.name;
}
Singleton.singleton = function(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}

var a = Singleton.singleton("a");
var b = Singleton.singleton("b");
console.log(a === b); // true

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Singleton = function(name) {
this.name = name;
}

Singleton.prototype.getName = function() {
return this.name;
}
Singleton.singleton = (function() {
var instance = null;
return function(name) {
if(!instance) {
instance = new Singleton(name);
}
return this.instance;
}
})();
var a = Singleton.singleton("a");
var b = Singleton.singleton("b");
console.log(a === b) //true

这种写法的问题时具有不透明性,而且必须要用Singleton.singleton来获取对象。

透明的单例模式

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var CreateDiv = (function() {
var instance = null;
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.appendChild(div);
};
return CreateDiv;
})();

var a = new CreateDiv("a");
var b = new CreateDiv("b");
console.log(a === b)

CreateDiv做了两件事情:1.保证只有一个对象。 2.创建对象和执行init方法。 这就违背了单一职责原则。
假设某天我们要创建上千上万多div,即要让这个类从单例类变成一个普通的可产生多个实例的类。

用代理模式实现单例模式

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
var CreateDiv = function(html) {
this.html = html;
this.init();
}

CreateDiv.prototype.init = function() {
var div = document.createElement("div");
div.innerHTML = this.html;
document.body.appendChild(div);
};

var ProxySingletonCreateDiv = (function() {
var instance;
return function(html) {
if(!instance) {
instance = new CreateDiv(html);
}
return instance;
}
})()
var a = new ProxySingletonCreateDiv("a");
var b = new ProxySingletonCreateDiv("b");
console.log(a === b); // true
console.log(a instanceof CreateDiv) //true
console.log(b instanceof ProxySingletonCreateDiv) //false

我们把管理单例的逻辑移到了ProxySingletonCreateDiv中,这样一来,CreateDiv就变成了普通的类

惰性单例模式

惰性单例指的是在需要时才创建对象实例,本文刚开始的单例模式就是直到调用singleton的时候才创建对象实例。
假设有一个需求是WebQQ的页面中,点击QQ头像时,弹出登陆浮窗,很明显这个浮窗是唯一的。

方案一

在加载页面的时候就创建好这个浮窗,并且这个浮窗在一开始就是隐藏的,只有被点击的时候,才显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<body>
<button id="loginBtn">Login</button>
</body>
<script>
var loginLayer = (function() {
var div = document.createElement("div");
div.innerHTML = '登录框';
div.style.display = 'none';
document.body.appendChild(div);
return div;
})();
document.getElementById('loginBtn').onclick = function() {
loginLayer.style.display = 'block';
};
</script>
</html>

这种方式有一个问题,那就是有时候并不想登陆,所以就白白创建了对象,浪费资源,下面改写下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<body>
<button id="loginBtn">Login</button>
</body>
<script>
var createLoginLayer = function() {
var div = document.createElement("div");
div.innerHTML = '登录框';
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
document.getElementById('loginBtn').onclick = function() {
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
};
</script>
</html>

虽然达到了惰性的效果,但是每次点击都会创建一个对象实例,也很浪费资源,所以接下来用一个变量来标记是否创建了对象实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
<body>
<button id="loginBtn">Login</button>
</body>
<script>
var createLoginLayer = (function() {
var div;
return function() {
if(!div) {
div = document.createElement("div");
div.innerHTML = '登录框';
div.style.display = 'none';
document.body.appendChild(div);
}
return div;
}
})();
document.getElementById('loginBtn').onclick = function() {
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
};
</script>
</html>

通用的惰性单例

上段代码仍然存在以下问题:

  1. 违反单一职责原则,创建和管理逻辑都放在createLoginLayer中。
  2. 如果下次需要创建唯一的iframe或者script标签,就相当于要把createLoginLayer几乎抄一遍:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var createIframe = (function() {
    var iframe;
    return function() {
    if(!iframe) {
    iframe = document.createElement("iframe");
    iframe.innerHTML = '登录框';
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
    }
    return iframe;
    }
    })();
    所以需要把不变的逻辑抽出来,这个逻辑始终是用一个变量来标志是否创建过对象
    1
    2
    3
    4
    var ojb;
    if(!obj) {
    obj = xxx;
    }
    现在我们将管理单例的逻辑抽出来,这些逻辑封装在getsingle函数内部,创建对象的方法fn被当作参数动态传入single函数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var getSingle = function(fn) {
    var result;
    return function() {
    return result || (result = fn.apply(this, arguements) );
    }
    };

    var createLoginLayer = function() {
    var div = document.createElement("div");
    div.innerHTML = '登录框';
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
    };

    var createSingleLoginLayer = getSingle(createLoginLayer);

    document.getElementById('loginBtn').onclick = function() {
    var loginLayer = createSingleLoginLayer();
    loginLayer.style.display = 'block';
    };
    下面再试试创建唯一的iframe:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var createSingleIFrame = getSingle(function() {
    var iframe = document.createElement("div");;
    document.body.appendChild(iframe);
    return iframe;
    });

    document.getElementById('loginBtn').onclick = function() {
    var iframe = createSingleIFrame();
    iframe.src = 'www.google.com';
    };

es6中的单例模式

1
2
3
4
5
6
7
8
9
class SingleTon {
constructor(name) {
if(!SingleTon.instance) {
this.name = name;
SingleTon.instance = this;
}
return SingleTon.instance;
}
}

用static方法优化下

1
2
3
4
5
6
7
8
9
10
11
12
class SingleTon {
constructor(name) {
this.name = name;
}

static getInstance(name) {
if(!SingleTon.instance) {
SingleTon.instance = new SingleTon(name);
}
return SingleTon.instance
}
}

typescript中的单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cache{
public static readonly instance: Cache = new Cache();
private constructor(){

}
private _data: {[id:string]: string} = {};

public set(key: string, value: string) {
this._data[key] = value
}

public get(key: string) {
return this._data[key];
}
}
Cache.instance.set("key", "value");
console.log(Cache.instance.get("key"))
Author: Rick
Link: https://rcrick.github.io/2019/07/23/单例模式/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付寶