JavaScript 设计模式——单例模式

1. 什么是单例模式

单例模式 是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。

2. 单例模式的实现

2.1 简单实现

用一个变量标志当前是否已经为某类创建过对象,如果是,则下一次获取实例时直接返回之前创建好的对象。

const Singleton = function(name){
  this.name = name;
}
Singleton.prototype.getName = function() {
  console.log(this.name)
}
// 单例的实现
Singleton.getInstance = (function() {
  let instance = null;
  return function(name) {
    if(!instance) {
      instance = new Singleton(name);
	}
	return instance;
  }
})()
// 使用 & 验证
const a = Singleton.getInstance('instance1');
const b = Singleton.getInstance('instance2');
console.log(a === b);		// true

这种方式有一个问题,那就是使用 Singleton 类时必须知道它是一个单例类,且不通过 new XXX 的方法来获取对象,一定要使用 Singleton.getInstance 来获取对象。

2.2 透明的单例模式

下面我们创建一个可以直接使用 new XXX 的“透明”的单例类

const Singleton = (function(){
  let instance;
  // (*)
  const Singleton = function(name){
	if(instance){		// (**)
	  return instance
	}
	this.name = name;	//(***)
  }
  return Singleton;
})();

观察上面的构造函数(*),它实际上负责了两件事情:1. 创建对象(***) ;2. 保证只有一个对象(**)。这违反了“单一职责原则”。如果未来要把这个单例类改成普通的可以产生多个实例的类,必须改写 Singleton 构造函数。

2.3 用代理实现的单例模式

我们引入代理类来解决 2.2 中提到的问题

const Singleton = function(name) {
  this.name = name;
}
// 引入代理类
const ProxySingleton = (function(){
  let instance;
  return function(name){
	if(!instance){
      instance = new Singleton(name);
	}
	return instance
  }
})();
// 使用 & 验证
const a = new ProxySingleton('instance1');
const b = new ProxySingleton('instance2');
console.log(a === b);		// true

在此,我们把管理单例的逻辑移到了代理类 ProxySingleton 中,Singleton 只是一个普通的类。

3. JavaScript 中的单例模式

单例模式的核心是确保只有一个实例,并提供全局访问
JavaScript 是一门无类(class-free)语言,且用 JavaScript 创建对象非常简单。既然我们只要一个唯一的对象,那为它先创建一个“类”显得有些多余 了。

对于全局变量,比如 const a = {},它是独一无二的,且提供给全局访问,满足了单例模式的两个条件。

但是全局变量很容易造成命名空间污染。以下几种方式可以相对降低全局变量带来的命名污染。

3.1 使用命名空间

  1. 直接用对象字面量
const namespace1 = {
  a: function() {
	alert(1);
  },
  b: function() {
	alert(2);
  }
}
  1. 动态创建命名空间
const MyApp = {};
MyApp.namespace = function(name) {
  const parts = name.split('.');
  let current = MyApp;
  for(let i in parts) {
	if(!current[parts[i]){
      current[parts[i]] = {}
	}
	current = current[parts[i]];
  }
}
MyApp.namespace('event');
MyApp.namespace('dom.style');
console.dir(MyApp);
/* 结果
{
  "event": {},
  "dom": {
    "style": {}
  }
}
*/

3.2 使用闭包封装私有变量

const user = (function(){
  let __name = 'Li Hua';
  let __age = 18;
  return {
	getUserInfo: function() {
	  return __name + '-' + __age;
	}
  }
})()

4. 惰性单例

惰性单例 指的是在需要时才开始创建对象实例。

如,点击某按钮后弹出登录浮窗,关闭浮窗后,再次点击该按钮弹出之前创建好的浮窗。

const createLoginLayer = (function(){
  let div;
  return functuin(){
	if(!div){
	  div = document.createElement('div');
	  div.innerHTML = '登录浮窗';
	  div.style.display = 'none';
	  document.body.appendChild(div);
	}
	return div
  }
})();

document.getElementById('loginBtn').onclick = function() {
  const loginLayer = createLoginLayer();
  loginLayer.style.display = 'block';
}

上面的代码仍然有一些问题

  • createLayer 既负责创建对象,又负责处理单例逻辑。
  • 如果下次要创建唯一的 ifram,那么要创建一个和 createLoginLayer 几乎一样的方法
const createIframe = (function(){
  let iframe;
  return functuin(){
	if(!iframe){
	  iframe = document.createElement('iframe');
	  iframe.style.display = 'none';
	  document.body.appendChild(iframe);
	}
	return iframe
  }
})();

下面将单例逻辑抽离出来。

const getSingle = function(fn){
  let = res;
  return function() {
	return res || (res = fn.apply(this, arguments));
  }
}

const createLoginLayer = function(){
  div = document.createElement('div');
  div.innerHTML = '登录浮窗';
  div.style.display = 'none';
  document.body.appendChild(div);
  return div;
}

const createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById('loginBtn').onclick = function() {
  const loginLayer = createSingleLoginLayer();
  loginLayer.style.display = 'block';
}

如果想在页面第一次渲染的时候为元素绑定事件,也可以用上面的 getSingle

const bindEvent = getSingle(function() {
  document.getElementById('click').onclick = function() {
	alert('click');
  }
  return true;
})
var render = function() {
  console.log('开始渲染页面');
  bindEvent();
}

render();
render();
render();

render()bindEvent() 都分别执行了3次,但对应元素上只绑定了一个事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值