单例模式的定义:保证一个类仅有一个实例,并提供一个它的全局访问点
如果想实现一个标准的单例模式其实并不复杂。无非是用一个变量来标志是否已经为当前对象创造一个实例。如果是,则直接返回这个实例对象,如果不是,则创建。
function Person(name,age) {
this.name = name
this.age = age
}
Person.prototype.getName = function() {
alert(this.name)
}
Person.getInstance = function(name,age) {
if(this.instance) {
return this.instance
}
this.instance = new Person(name,age)
return this.instance
}
let a = Person.getInstance("小红",18)
console.log(a)
let b = Person.getInstance("小明",20)
console.log(b)
console.log(a == b) =>true
我们通过 Person.getInstance 来获取 Person类的唯一对象,这种方式相对简单,但有 一个问题,就是增加了这个类的“不透明性”,Person类的使用者必须知道这是一个单例类, 跟以往通过 new XXX 的方式来获取对象不同,这里偏要使用 Person.getInstance 来获取对象
其实,在JS中的单例模式相对于其它语言会显得更加直接,因为JS是一门无类的语言,搬用其它以类为中心的语言并没有意义
单例模式的两个重要特点,就是全局只有一个,并提供全局访问
因此,在开发中我们有时会把全局对象当成一种单例来使用,例如:
const a = {}
这样创建的对象的确符合单例模式的两个特点,但是全局对象容易造成命名空间污染。在大中型项目中,如果不加限制,会很容易重复申明这些变量 。
我们可以采用以下方式来避免创建全局对象造成的命名空间污染问题
1.使用命名空间
var namespace1 = {
a: function(){
alert (1);
},
b: function(){
alert (2);
}
};
2.使用闭包封装私有变量
var user = (function() {
var _name = "小明"
var _age = 18
return {
getName:function() {
return _name
}
}
})()
上面我们了解了单例模式的特定场景下的一些实现办法,现在我们来思考如何去实现一个通用的单例模式
我们需要把不变的部分隔离出来,管理单例的逻辑其实是完全可以抽象出来的,这个逻辑始终是一样的:用一个变量来标志是否创建 过对象,如果是,则在下次直接返回这个已经创建好的对象
function getSingle(fn) {
var result
return function() {
return result || (result = fn.apply(this,arguments))
}
}
这样一个通用的单例模式就实现了,下面我们通过一个实际开发中的应用场景来检测下他的功能吧
1.我们经常在绘制页面中会有这种场景,我们需要绘制一个列表,并且在绘制完列表后,还需要为列表绑定事件
function render() {
//列表页面渲染
bindEvent() //事件绑定
}
function bindEvent() {
document.getElementById('body').onClick = function() {
console.log("绑定事件")
}
}
这样当列表每次重新渲染的时候就会造成事件也被重新绑定了,这时,我们就可以借助于单例函数来控制事件绑定函数只会执行一遍
function render() {
//列表页面渲染
bindEvent() //事件绑定
}
let bindEvent = getSingle(function() {
document.getElementById('body').onClick = function() {
console.log("绑定事件")
}
return true
})
这样。即使多次调用render函数,bindEvent的事件绑定也只会触发一次