单例模式
创建形的单例模式在前端实际应用中使用场景较多,尤其在于写框架与组件中使用率较高。
介绍/概述:
限制类实例化次数只能一次,确保一个类只有一个实例,并提供一个访问它的全局访问点。
代码演示
例子:简单实现一个全局的 windows 类,保证全局只有一个。
“简单版” 单例模式:
1.ES5 实现
function NewWindow(name) {
this.name = name;
}
NewWindow.prototype.getName = function () {
return this.name;
}
//这是构造函数的方法,只能通过构造函数类来访问,而不能通过实例来访问
NewWindow.getInstance = (function () {
let instance;
return function (name) {
if (!instance) {
instance = new NewWindow(name);
}
return NewWindow;
}
})();
let w1 = NewWindow.getInstance();
let w2 = NewWindow.getInstance();
console.log(w1 === w2) // true
代码中定义了一个 NewWindow
的全局构造函数(也可类似一个全局变量),然后为其添加一个 getInstance()
方法来控制单例;
getInstance()
方法中采用闭包,存储了一个闭包变量,来达到无论调用多少次 getInstance()
,最终都只会返回一个实例对象。
2. ES6类实现
/**
* 模仿一个全局 window 对象
*/
class NewWindow {
// 定义一个静态属性
private static instance: NewWindow;
// 实例方法
public static getInstance() {
if(!NewWindow.instance) {
NewWindow.instance = new NewWindow();
}
return NewWindow.instance;
}
}
// 获取 NewWindow 的实例
let w1 = NewWindow.getInstance();
let w2 = NewWindow.getInstance();
console.log(w1 === w2); // true
ES6 与 ES5 实现逻辑类似,都是定义了一个局部私有变量来进行保存,达到最终返回的永远是同一个实例。
以上实现的缺点:
- 不够“透明”,无法使用
new
来进行类实例化; - 使用者必须主动调用
getInstance()
方法,使用麻烦; - 功能代码耦合在一起,不符合 “单一职责原则”
“透明版” 单例模式:
主要解决:统一使用
new
操作符来获取单例对象, 而不是NewWindow.getInstance()
。
//透明单例
let CreateWindow = (function () {
let newInstance;
return function (name) {
if (newInstance) {
return newInstance;
}
this.name = name;
return (newInstance = this);
}
})();
// 首先大家要弄懂 new 一个构造函数,主要做了那几件事
// 结合起来就明白这其中的 newInstance = this
let w1 = new CreateWindow();
let w2 = new CreateWindow();
console.log(w1 === w2); // true
但是现在虽然是可以用 new 创建了,但是还有个小问题,就是不够灵活,其中的 this.name = name 没有与对象创建操作进行拆分,不符合“单一职责原则”。
“代理版“ 单例模式:
主要解决:将管理单例操作,与对象创建操作进行拆分,实现更小的粒度划分,符合“单一职责原则”
// 单独一个 NewWindow 类,处理实例对象,做到分离
function NewWindow(name) {
this.name = name;
}
NewWindow.prototype.getName = function () {
console.log(this.name);
}
// CreateWindow 只需要管理单例的操作创建
let CreateWindow = (function () {
let instance;
return function (name) {
if (!instance) {
instance = new NewWindow(name);
}
return instance;
}
})();
let w1 = new CreateWindow('window1');
let w2 = new CreateWindow('window2');
console.log(w1 === w2);
console.log(w1.getName()); // 'window1'
console.log(w2.getName()); // 'window1'
“惰性单例” 模式
主要解决:需要时才创建类实例对象。对于懒加载的性能优化,想必前端开发者并不陌生。惰性单例也是解决 “按需加载” 的问题。
例子:页面弹窗提示,多次调用,都只有一个弹窗对象,只是展示信息内容不同。
- 开发这样一个全局弹窗对象,我们可以应用单例模式。为了提升它的性能,我们可以让它在我们需要调用时再去生成实例,创建 DOM 节点。
let getSingleton = function(fn) {
var result;
return function() {
return result || (result = fn.apply(this, arguments)); // 确定this上下文并传递参数
}
}
let createAlertMessage = function(html) {
var div = document.createElement('div');
div.innerHTML = html;
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
let createSingleAlertMessage = getSingleton(createAlertMessage);
document.body.addEventListener('click', function(){
// 多次点击只会产生一个弹窗
let alertMessage = createSingleAlertMessage('您的知识需要付费充值!');
alertMessage.style.display = 'block';
})
单例模式的应用场景
1.引用第三方库(多次引用只会使用一个库引用,如 jQuery)
if(window.jQuery!=null){
return window.jQuery;
}else{
// init do something
}
2. 弹窗(登录框,信息提升框)
3. 淘宝购物车 (一个用户只有一个购物车)
4. 全局态管理 store (Vuex / Redux)
//redux 整个应用只有一个仓库,整个仓库只有一个状态state
function createStore(reducer) {
let state;
let listeners = [];
function subscribe(listener) {
listeners.push(listener);
}
function getState() {
return state;
}
function dispatch(action) {
state = reducer(state, action);
}
return {
getState,
dispatch,
subscribe
}
}
let reducer = function () {}
let store = createStore(reducer);
参考地址: