单例模式
核心:确保只有一个实例,并提供全局访问 (注意:全局变量不是单例模式)
实现:用一个变量标志当前是否已经为某个类创建过对象,如果是,则在下次创建类实例的时候,直接返回之前创建的对象(用闭包缓存数据),若不是则通过new创建一个新的对象
应用场景:登录框
透明的单例模式
需求:实现一个透明的单例类,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。
为了把标志变量instance封装起来,此处使用自执行的匿名函数和闭包
const CreateDiv=(function(){
let instance;//标记变量
const CreateDiv=function(html){
if(instance){//若已创建,直接返回之前创建的对象
return instance;
}
//否则重新创建
this.html=html;
this.init();
return instance=this;
}
CreateDiv.prototype.init=function(){
const div=document.createElement('div');
div.innerHTML=this.html;
document.body.appendChild(div);
}
return CreateDiv;
})();
const a=new CreateDiv('amethyst');
const b=new CreateDiv('lanlan');
console.log(a===b);//true,实际上只有一个innerHTML为amethyst的对象
缺点:
- CreateDiv函数实际上负责了两件事(违背了单一职责原则):
- 创建对象和执行初始化方法init()
- 保证只有一个对象
- 若要将单例类变成一个可以产生多个实例的普通类,需要改写CreateDiv构造函数,把控制唯一对象那一段代码去掉,操作繁琐且效率低
用代理实现单例模式
通过代理的方式解决上述问题
实现思路:
-
先在CreateDiv中,把控制唯一对象的部分移出去,使CreateDiv成为一个普通的创建div的类
-
const CreateDiv = function (html) { this.html = html; this.init(); } CreateDiv.prototype.init = function () { const div = document.createElement('div'); div.innerHTML = this.html; document.body.appendChild(div); }
-
引入代理类proxySingletonCreateDiv
-
const proxySingletonCreateDiv = (function () { let instance; return function (html) { if (instance) return instance; return instance = new CreateDiv(html); } })();
-
测试
-
//普通类 const a = new CreateDiv('amethyst'); const b = new CreateDiv('lanlan'); //单例类 const a1 = new proxySingletonCreateDiv('amethyst'); const b1 = new proxySingletonCreateDiv('lanlan'); console.log(a === b);//false console.log(a1 === b1);//true
惰性单例
- 指的是在需要的时候才创建对象实例
应用场景:网页登录框
实现:也是用一个变量标记是否已经创建过登录框
通用的惰性单例:
-
把管理单例的逻辑抽离出来,封装在getSingle函数内部,创建对象的方法被当做参数动态传入getSingle函数
-
const getSingle=function(fn){ let instance; return function(){ if(instance) return instance; return instance=fn.apply(this,arguments); } }
-
调用getSingle的时候,将用于创建登录框的函数fn以参数形式传入(还可以传入创建iframe等其他的函数),之后getSingle返回一个新的函数,变量instance用于保存fn的计算结果,并且instance处在闭包中,永远不会被销毁
-
若instance已有值,说明已经创建过对象,直接返回已创建的
-
创建登录框
-
//创建登录框的函数 const createLoginLayer = function () { const div = document.createElement('div'); div.innerHTML = '我是登录框'; div.style.display = 'none'; document.body.appendChild(div); return div; } //将createLoginLayer包装成创建单例的函数 const createSingleLoginLayer = getSingle(createLoginLayer); //在需要使用的时候调用createSingleLoginLayer获取单例 const btnObj = document.querySelector('#btn'); btnObj.addEventListener('click', function () { const login = createSingleLoginLayer(); login.style.display = 'block'; });
-
创建iframe
-
//创建iframe const createSingleIframe = getSingle(function () { const iframe = document.createElement('iframe'); document.body.appendChild(iframe); return iframe; }); document.querySelector('#btn1').addEventListener('click',function(){ const iframe=createSingleIframe(); iframe.src='https://www.baidu.com/'; });
单例模式是一种简单且非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个,并且创建对象和管理单例的职责被分布在两个不同的方法中,结合起来即可完成创建唯一实例对象的功能