单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
使用场景:
- 有一些对象往往只需要一个,就可以使用,比如线程池、全局缓存、浏览器中的 window 对象等;登录框浮窗会多次使用,登录浮窗就适合用单例模式
实现单例模式:
用一个变量标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该实例的时候,直接返回之前创建的对象。
const Singleton = function (name) {
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function () {
console.log(this.name);
};
Singleton.getInstance = function (name, type) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
};
const a = Singleton.getInstance("a");
const b = Singleton.getInstance("b");
console.log(Singleton.getInstance()); // {name: 'a', ...}
console.log(a === b); // true
console.log(a.name); // a
console.log(b.name); // a
/**
* 1. 创建对象和执行初始化init方法
* 2. 保证只有一个对象
*
* 如果要创建千千万万的div。要让这个类从单例变成一个普通的可产生多个实例的类,那我们必须要改造CreateDom构造函数,把控制唯一那一段去掉。可以使用代理实现单例模式
*/
const CreateDom = (function () {
let instance;
const Create = function (html) {
if (instance) {
return instance;
}
this.html = html;
this.init();
return (instance = this);
};
Create.prototype.init = function () {
const div = document.createElement("div");
div.innerHTML = this.html;
document.body.appendChild(div);
};
return Create;
})();
const a = new CreateDom("a");
const b = new CreateDom("b");
console.log(a === b); // true
-
使用代理实现单例模式
通过引入代理类的方式,把负责管理单例的逻辑移到了代理类 ProxySingleCreateDiv 中。这样一来,CreateDiv 就变成了一个普通的类,需要跟 ProxySingleCreateDiv 组合起来可以达到单例模式的效果
// 代理实现单例模式;
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);
};
// 引入代理类
const ProxySingleCreateDiv = (function () {
let instance;
return function (html) {
if (!instance) {
return new CreateDiv(html);
}
return instance;
};
})();
const a1 = new ProxySingleCreateDiv("a1");
const b1 = new ProxySingleCreateDiv("b1");
console.log(a1 === b1); // false
惰性单例:
创建实例对象和管理单例的职责分别放置在两个方法里,这两个独立变化而互不影响,当它们连在一起的时候,就完成了创建唯一实例对象的功能。
惰性单例是在需要的时候才创建对象实例。
-
符合单一职责原则
- 例子:
const getSingle = function (fn) { let result; return function () { return result || (result = fn.apply(this, arguments)); }; }; const creataLoginLayer = function () { const div = document.createElement("div"); div.innerHTML = this.html; div.style.display = "none"; document.body.appendChild(div); return div; }; const creataSingleLoginLayer = getSingle(creataLoginLayer);
- 也可以创建 iframe…
- 如果要给列表绑定 click 事件,如果是通过 ajax 动态往列表里追加数据,在使用事件代理的前提下,click 事件实际上只需要在第一次渲染列表的时候被绑定一次,可以使用 getSingle 函数
const btnEvent = getSingle(function () { document.getElementById("div1").onclick = function () { console.log("div1"); }; return true; }); const render = function () { console.log("开始渲染"); btnEvent(); }; render(); render(); render();
render和btnEvent分别执行了3次,但是div只绑定了一次事件