单例模式的定义:一个类只能创建一个实体对象。可能遇到的场景线程池的创建,全局缓存的创建等等。
结合JavaScript的语言特点,如果要实现单例,只需要通过一个全局变量来作为标志位,每次创建对象的时候都去判断一次,如果之前已经创建过则直接返回,没有则new一个出来,上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| var instance = null; var Person = function() { this.name = 'hahacoo' } Person.create = function() { if(!instance){ instance = new Person() } return instance } var p1 = Person.create(); var p2 = Person.create(); console.log(p1 === p2); //true
|
以上就是一个简单的单例模式,但是和以往我们通过new的方式创建一个对象还是有很大的区别。而且全局变量的创建也造成了变量的污染。下面是改进后的代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var Person = function() { this.name = 'hahacoo' } var ProxySingleton = function(fn) { var instance; return function() { if(!instance) { return new fn() } return instance; } }) var SingletonPerson = ProxySingleton(Person); var p1 = new SingletonPerson(); var p2 = new SingletonPerson(); console.log(p1 === p2); //true
|
通过代理,将自定义类和单例实现的逻辑分开,符合单一职责的原则,也符合我们的书写习惯。
JavaScript作为一门函数式脚步语言,不仅仅局限在面向对象,对于函数编程如何实现单例模式呢?我们在做web开发时,经常会有这样的需求,对于多次操作只希望有一个dom对象被更改,譬如说对话框,提示框,也同样符合单例模式的特点,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| var showDialog = function() { var dialog = document.createElement('div'); dialog.style.display = 'none'; document.body.appendChild(dialog); return dialog; } var button = document.getElementById('#show'); var singletonDialog = function(fn) { var dialog; return function() { if(!dialog) { dialog = fn.apply(this, arguments) } return dialog } } var dialogDom = singletonDialog(showDialog); button.onclick = function() { var dialog = dialogDom(); dialog.style.display = "static"; }
|
如上,在页面就只会存在一份dom节点,确保不会重复创建。
单例模式的关键就是遵循单一职责原则,将我们的业务逻辑代码和单例实现代码分开,灵活使用闭包和高阶函数,保证对象的唯一性。