好久好久没有写博客了。写博客是个很好的习惯,现在重新开始吧。
国内谈js的设计模式的书也就那么几本,找了本javascript设计模式与开发实战(曾探)
开始学习。9102年要努力起来。进入正文吧。以下代码全部使用es6实现。
单例模式的定义是:保证一个类只有一个实例,并提供一个访问它的全局访问点。
核心就是确保只有一个实例,并提供全局访问。
因为有些内容我们只需要一个,比如线程池,全局缓存,window对象。在页面中,比如登录弹窗,无论点击多少次登录按钮,页面中只有一个登录框,那么它就是唯一的,适合用单例模式创建。以下单例都是惰性单例,就是说在使用的时候才会创建它, 这样是很合理的。
1.实现单例模式
class SingleTon {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
static getInstance(name) {
if (!this.instance) {
SingleTon.instance = new SingleTon(name);
}
return SingleTon.instance;
}
}
SingleTon.instance = null;
const a = SingleTon.getInstance('小鸡');
const b = SingleTon.getInstance('小鸟');
console.log(a === b); // true
复制代码
调用的时候通过SingleTon.getInstance来获取单例对象,这种方式编写方式相对简单,但是也增加了这个类使用的复杂度,原因在于开发者必须知道要通过SingleTon.getInstance来创建单例对象,而不是更为熟悉的new SingleTon()来创建。接下来改造一下。
2.透明的单例模式
这里要通过new xxx()来构建单例,这种方式更为熟悉。假设页面中需要一个div(可以假想成登录框),并且只需要这一个。
const CreateDiv = (() => {
let instance = null;
class CreateDiv {
constructor(html) {
if(!instance) {
instance = this;
this.html = html;
this.init();
}
return instance;
}
init() {
const div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
}
return CreateDiv;
})();
const a = new CreateDiv('登录框内容小鸡');
const b = new CreateDiv('登录框内容小鸟');
console.log(a === b); // true
复制代码
这种方式相当于原始的SingleTon.getInstance有了改善,但是它仍然存在问题。 这段代码中class CreateDiv实际上负责了两件事情。第一是创建对象并且保证只存在一个对象,第二是执行初始化方法。这让人感觉很不好,它违背了单一职责原则。
假如某天需要利用这个类,在页面中创造div都由它负责,那么要改动这个类的代码,把控制创建唯一对象的这一段去掉,这种修改虽然不是很麻烦,但是它是可以避免的。所以我们可以将上述的两件事情分开,分别用两段代码实现,保证单一职责原则。
3.用代理实现单例模式
class CreateDiv {
constructor(html) {
this.html = html;
this.init();
}
init() {
const div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
}
const proxySingleTonCreateDiv = (() => {
let instance = null;
return html => {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
};
})();
// 这里的new需不需要都可以,为了称作代理类,还是加上了
const a = new proxySingleTonCreateDiv('小鸡');
const b = new proxySingleTonCreateDiv('小鸟');
console.log(a === b); // true
复制代码
在这里,CreateDiv只负责创造div,proxySingleTonCreateDiv是个代理类。它们组合起来就可以达到单例模式的效果,并且不同的类职责单一了。就算想创建多个div也可以达到了。
这种方式已经足够完美,不够还可以继续改进。那就是proxySingleTonCreateDiv只能代理CreateDiv这个类,如果后来想要代理CreateIframe、CreateScript它可就无能为力了,所以接下来的改造就是要达到代理任何类的目的。
4.通用的惰性单例
/*
var getInstance = function(fn) {
var result = null;
return function() {
return result || (result = fn.apply(this, arguments));
};
};
*/
class GetInstance {
constructor(fn) {
let result = null;
return () => {
return result || (result = fn.apply(this, arguments));
};
}
}
var createDiv = new GetInstance(function() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
});
var createIframe = new GetInstance(function() {
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
return iframe;
});
const div = createDiv();
const div2 = createDiv();
div.innerHTML = '小鸡';
div2.innerHTML = '小鸟';
console.log(div === div2); // true
const iframe = createIframe();
const iframe2 = createIframe();
console.log(iframe === iframe2); // true
复制代码
如上,GetInstance没必要使用class的,只是为了使用es6的写法。可以使用注释部分代码,就不需要使用new了。
这种写法是通用的写法,GetInstance中传入你想要构造的内容,GetInstance保证它是单例,很类似react的高阶组件。然后createXXX()返回创建的对象,你还可以自定义对象的内容,比如上述的div和div2就是了,它们是同一个对象。也可以在GetInstance传入的方法中去给创建的div去赋默认值。