单例模式
简单的单例模式实现
需要用一个变量来标记是否已经创建过一个实例对象
var Single = function(name){
this.name = name;
this.instance = null;
}
Single.getInstance = function(name){
if(!this.instance){
this.instace = new Single(name)
}
return this.instance
}
var a = Single.getInstance("a");
var b = Single.getInstance("b");
a===b //true
缺点:不透明,相比于正常的 new 创建实例,创建单例必须用 getInstance 方法.
透明的单例模式
目标是实现一个透明的单例类,下面将使用 CreateDiv 单例类,在页面创建唯一的 div 节点
var CreateDiv = (function(){
var instance = null;//自执行函数保证instance只初始化一次
function CreateDiv(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("a");
var b = new CreateDiv("b");
a===b//true
为了将 instance 封装起来使用了自执行匿名函数和闭包 增加了复杂性 并且 CreateDiv 负责了两件事 第一是创建对象和执行 init 方法 第二是保证只有一个对象 。违背了单一职责原则
用代理实现单例模式
为了解决上述的单一职责问题 下面引入代理模式实现这个单例模式
首先把创建对象和执行 init 方法分离出来
function CreateDiv(html){
this.html = html;
this.init();
}
CreateDiv.prototype.init = function(html){
var div = document.createElement("div");
div.innerHTML = this.html;
document.body.append(div);
}
接下来引入代理类
var createDivProx = (function(){
var instance;
return function(html){
if(!instance){
instance = new CreateDiv(html)
}
return instance
}
})()
var a = new createDivProx("a")
var b= new createDivProx("b")
a==b//true
Js 的单例模式-使用闭包封装的私有变量
既然我们需要的是一个对象 那就没必要先创建一个类
为了避免全局变量的滥用,可以把变量封装在闭包内部 只暴露接口和外界通信
var user = (function(){
var _name = "yosgi"
var _age = 29;
return {
getUserInfo(){
return _name+"-"+_age
}
}
})()
或者使用 es6 的 Symbol
var _name = Symbol.for('name');
var _age = Symbol.for('age');
window[_name] = "yosgi"
window[_age] = 29//_name 和_user不会被无意中覆盖
完成一个惰性单例
惰性单例指在需要的时候才创建实例对象。
下面完成一个需求是 当点击 login 按钮时被创建的 div 框(以后也有可能是 iframe 框)。
var createLoginLayer = (function () {
var div;
return function () {
if (!div) {
div = document.createElement("div");
div.style.display = "none";
document.body.appendChild(div);
}
return div
}
})()
document.getElementById("login").onclick = function () {
var LoginLayer = createLoginLayer()
LoginLayer.style.display = 'block';
};
为了解决单一职责的问题,将维护单一实例的代码提取出来
//getSingle用来管理单例
var getSingle = (function(){
var result;
return function(fn){
if(!result){
//闭包使result可以保存fn的结果
result = fn.apply(this,arguments)
}
return result
}
})()
//createSingleIframe用来创建实例
var createSingleIframe = function(){
iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
return iframe
}
document.getElementById("login").onclick = function(){
var Iframe = getSingle(createSingleIframe)
Iframe.style.display = 'block';
Iframe.src="http://baidu.com"
};
单例模式的应用
在渲染页面中的一个列表之后,绑定 addEventListener click 事件 ,如果是通过 ajax 动态添加数据,实际上只需要在第一次渲染绑定一次,如果不想判断是第几次渲染 可以利用 getSingle
var getSingle = (function(){
var result;
return function(fn){
if(!result){
result = fn.apply(this, arguments)
}
return result
}
})()
var bindEvent = function(){
document.getElementById("div1").addEventListener("click",()=>{
console.log("click")
})
return true
}
var render = function() {
getSingle(bindEvent)
}
render()
render()
render()
可以注意到,点击 div1 多次也只会触发一次点击事件(当然也可以使用 onclick,一个 click 在同一时间只能指向唯一的对象 这点我以前没注意)