单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。
下面实现一个简单的懒汉单例,即使多次调用new操作符,得到的也是同一个实例:
const SingletonPerson = function () {
let _count = 0;
this.getCount = function () {
return _count;
};
this.setCount = function (count: number) {
_count = count;
};
};
SingletonPerson.prototype.plusCount = function () {
this.setCount(this.getCount() + 1);
};
SingletonPerson.getInstance = (function () {
let _instance = null;
return function () {
if (!_instance) {
_instance = new SingletonPerson();
}
return _instance;
};
})();
const person1 = SingletonPerson.getInstance();
const person2 = SingletonPerson.getInstance();
let time = 100;
while (time--) {
person1.plusCount();
}
console.log(person1.getCount()); // 100
console.log(person2.getCount()); // 100
console.log(person1 === person2); // true
其中,getInstance 函数立即执行,后面调用的其实是返回的函数,这样就用闭包把 _instance 保存了起来,而不是执行一次一个_instance。
上面这样虽然的确实现了单例模式,但是要通过调用getInstance的方法才能获取实例,和一般直接new一个对象的使用方法有差别。”这个类的创建是否使用了单例模式“这件事应该是被封装起来的,除了名称上给用户提示让用户知道这是个单例之外,其使用成本不应该由用户来承担,在用户看来,应该是直接 new 出实例才符合逻辑。
下面改用使用new操作符创建单例,让单例无法从创建语法上与其他实例的创建区别开来,从而变成透明。
new运算符有这样的性质:当用 new 运算符调用函数时,该函数会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。但是如果构造器显式地返回一个Object类型对象,那么此次运算结果就是这个Object对象,而不是构造函数中的this。利用这个性质我们可以编写透明的单例:
const SingletonPerson = (function () {
let _instance = null;
function Person() {
// Person 行为和属性。。。。
if (!_instance) {
_instance = this;
}
return _instance;
};
return Person
})();
const person1 = new SingletonPerson();
const person2 = new SingletonPerson();
console.log(person1 === person2); // true
执行上面的代码可以看到,用户只是正常地new了两个对象,但是实际上得到的是同一个实例。
但是上面的代码依然是有不好的地方,根据单一职责问题,Person函数应该只负责与Person属性和行为相关的事务,而 “Person本身单例的创建和维护” 这件事是Person函数外部的功能,不应该是函数本身需要关心的,应该将其拿出。可以使用代理模式,通过代理类创建Person的单例。
function Person() {
// Person 行为和属性。。。。
}
const ProxySingletonPerson = (function () {
let _instance = null;
return function () {
if (!_instance) {
_instance = new Person();
}
return _instance;
};
})();
const person1 = new ProxySingletonPerson();
const person2 = new ProxySingletonPerson();
console.log(person1 === person2); // true
这样,Person就变成了一个普通的类,创建和维护Person的单例的任务就交给了ProxySingletonPerson类来维护。