JavaScript设计模式——单例模式

本文由《JavaScript设计模式与开发实践》–曾探著总结而来。

应用场景

某个作用域内(如全局作用域)只需要一个具备功能的对象。例如全局的window,登录框等等。总之,就是在顶级的作用域中用来掌控顶级信息的对象。这样的对象是唯一的,适合用单例模式来创建。

创建方式

思路:包括两点,

  1. 首先要创建这样的对象。创建对象可以用ES6语法中的类(或ES5的构造函数),通过new 类名()来创建,也可以用对象字面量语法创建。
  2. 其次要保证对象唯一。保证唯一可以采用只调用一次类的构造函数,再次调用直接返回已创建好的对象。当然,对象字面量语法创建的对象本身就是唯一的。
方式一:getInstance()方法返回单例

利用类来创建,但不通过new方式调用,而是调用专门创建单例的方法。

// 例如创建一个存储数据的Store对象
// 在类上挂载静态属性来区分实例是否创建
class Store {
	constructor(props) {}
	Store.instance = null; // 在类上挂载静态属性instance来判断是否已经创建了实例
	Store.getInstance = function(props) {
		if(!this.instance) {
			this.instance = new Store(props);
		}
		return this.instance;
	}
}
var store1 = Store.getInstance(props1);
var store2 = Store.getInstance(props2);
store1 === store2; // true
// 同样可以通过闭包变量来区分实例是否创建
class Store {
	constructor(props) {}
	Store.getInstance = (function() {
	// 声明一个闭包变量,用来判断实例是否已经创建
		var instance = null;
		return function(props) {
			if(!instance) {
				instance = new Store(props);
			}
			return instance;
		}
	})()
}

这种方式在思路上很清晰,但却存在着“不透明”的问题。使用的是Store.getInstance()来创建实例,而并非通用的new调用方式。另外,创建对象和保证唯一两种功能耦合在一起,不符合“函数单一职责”的原则,不利于代码复用和扩展。

方式二:new调用返回单例

使用类来创建,但用new调用,符合通常的创建实例方式。既然是用new调用,那么创建实例和判断唯一性就会在构造函数中进行。

class Store {
	Store.instance = null;
	constructor(props) {
		if(Store.instance) {
			return Store.instance;
		}
		// 执行一系列初始化this的逻辑;
		Store.instance = this;
		return Store.instance;
	}
}

这种方式解决了“透明”问题,但创建实例和保证唯一仍然耦合在一起。

方式三:代理模式,分离创建实例和保证唯一的耦合,将保证唯一的功能集成到代理函数中

利用代理模式,将创建实例的功能和保证唯一的功能分离开,各自集成在不同的函数中,从而可以使代码利于复用。这样创建实例的时候可以按正常方式创建很多实例,当需要创建单例时,用代理函数调用即可。

// 创建实例的功能由Store负责,保证唯一的功能由getStoreInstance函数负责;
class Store {
	constructor(props) {}
}
// getStoreInstance函数负责代理保证唯一的功能;
const getStoreInstance = (function() {
	var instance = null;
	return function(props) {
		if(!instance) {
			intance = new Store(props);
		}
		return instance;
	}
})()

对象字面量语法创建单例

对象字面量语法创建单例很方便,但容易污染全局作用域。由两种方式来减少污染:

  • 把单例置于命名空间中
  • 用闭包将单例变为私有变量

管理单例的逻辑

由上述代码发现,管理单例的逻辑(即如何保证唯一的逻辑)基本上类似于如下代码:

var instance = null;
if(!intance) {
	// 创建实例并赋予instance
}
return instance;

惰性单例

有的时候不需要页面加载时就创建单例,而是响应用户行为,或者在页面空闲时创建单例,这样会使页面性能提高。例如,登录框的创建,用户可能并不想登录,只是想浏览页面,此时的登录框适合用惰性单例的方式去创建,在用户点击登录按钮时创建登录框。

// html
<body>
	<button id="login">登录</button>
</body>
// script
<script>
// getInstance函数用来代理单例的管理,接收任何创建对象的方法,返回该单例对象。这样不管创建什么对象,都可以用getInstance函数来管理单例。
	const getInstance = (function() {
		var instance = null;
		// fn为创建DOM节点的函数,但不能是通过new调用的实例化函数;
		return function(fn) {
			var args = Array.prototype.slice.apply(arguments, 1); // args保存除fn外的其他参数;
			if(!instance) {
				instance = fn.apply(this, args);
			}
			return instance;
		}
	})()
	const createLogin = function() {
		var div = document.createElement('div');
		div.innerHTML = '请登录';
		div.style.display = 'none';
		document.body.appendChild(div);
		return div;
	}
	document.getElementById('login').addEventListener('click', function() {
		var div = getInstance(createLogin);
		div.style.display = 'block';
	});
</script>

单例模式的其他应用

在单例模式的管理逻辑中,实际上不只是创建单例对象,所有只需要执行一次的行为逻辑,都可以利用单例模式来管理。如jQuery中只绑定一次事件的one()方法,即可以用单例模式去实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值