百度百科定义
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
解释如下:
例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
要点
- 单例模式的类只提供私有的构造函数。
- 类定义中含有一个该类的静态私有对象。
- 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
简而言之:保证一个类仅有一个实例,并提供一个访问它的全局访问点
传统的单例模式-js实现
已经知道单例模式的要点是一个类仅有一个实例,因此可以设置一个变量来标志当前是否已经创建过对象,如果是,在后面获取该类实例时,直接返回之前创建过的对象
let Singleton = function(value) {
this.value = value
}
Singleton.prototype.getValue = function () {
console.log(this.vlaue)
}
Singleton.getInstance = (function () {
let instanceObj = null;
retrurn function (value) {
if( !instanceObj ) {
instanceObj = new Singleton(value)
}
return instanceObj
}
})()
验证下
let a = Singleton.getInstance( 'value1' );
let b = Singleton.getInstance( 'value2' );
alert(a===b); //true
这里必须利用Singleton.getInstance来获取Singleton类的唯一对象,使用此类时必须知道这是一个单例类,并不是那么直观,这样的实现方式具有不透明性,意义不大。下面改进下,直接用代理的方式实现一个具有透明性的单例模式
var CreateDiv = function(html) {
this.html = html;
this.init();
};
CreateDiv.prototype.init = function() {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
var ProxySingletonCreateDiv = (function() {
var instance;
return function(html) {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
}
})();
var a = new ProxySingletonCreateDiv('div1');
var b = new ProxySingletonCreateDiv('div2');
在这里,使用了自执行的匿名函数和闭包将instance封装,有些增加代码的复杂性,把管理单例的逻辑放在ProxySingletonCreateDiv代理类,CreateDiv只负责创建对象和初始化执行init方法,分工明确,这样的组合方式达到了单例模式的效果。
JavaScript的单例模式实现
js中的单例
- 把全局变量当成单例来使用
- 使用闭包封装私有变量
var a={};
当用这种方式创建对象 a 时,对象 a 确实是独一无二的。如果 a 变量被声明在全局作用域下, 则我们可以在代码中的任何使用位置这个变量,全局变量提供给全局访问是理所当然的。这样就满足了单例模式的两个条件。但是,全局变量存在问题–命名空间污染,因此要慎用,推荐使用对象字面量的方式
var namespace1 = {
a: function(){
alert (1);
},
b: function(){
alert (2);
}
};
使用闭包封装私有变量
var user = (function(){
var __name = 'sven',
__age = 29;
return {
getUserInfo: function(){
return __name + '-' + __age;
}
}
})();
JavaScript的单例实现
首先,有这样的一个场景:在前端页面,弹层都不陌生吧,点击某个按钮时需要在页面中弹出一个遮罩层,比如现在很多页面上的登录提示。
简单弹层so easy,伸手就来,但是我们考虑下以什么样的方式创建显示这个弹层:
- 页面加载完成便创建好,只是一开始隐藏
- 当用户点击的时候在创建弹层
实现下第一种方式,不管如何先创建好结构
<Html>
<body>
<button id="btn">点我</button>
</body>
<script>
let CreateModal = (function(){
let div = document.createElement( 'div' );
div.innerHTML = 'lalalalllal';
div.style.display = 'none';
return document.body.appendChild( div );
})();
document.getElementById( 'btn' ).onclick = function(){
CreateModal.style.display = 'block';
};
</Html>
这种方式每次都会初始化好这个div,哪怕我可能不需要用到,这样造成浪费DOM节点。看下实现下第二种方式是什么样的:
<script>
let CreateModal = function(){
let div = document.createElement( 'div' );
div.innerHTML = 'lalalalllal';
div.style.display = 'none';
return document.body.appendChild( div );
}
document.getElementById( 'btn' ).onclick = function(){
let divObj = CreateModal()
divObj.style.display = 'block';
};
</script>
这样的实现方式是实现了按需的目的,但却不是单例,因为每次点击按钮都会创建一个新的弹层,即使我们再实现一个关闭弹层的功能可以只保留一个,可是这样频繁的增加删除节点都不是明智之举。我们进行优化下,很容易想到的是如一开始的方式,可以加一个变量标识下:
<script>
let CreateModal = (function(){
let div
return function(){
if(!div) {
let div = document.createElement( 'div' );
div.innerHTML = 'lalalalllal';
div.style.display = 'none';
return document.body.appendChild( div );
}
return div
}
})();
document.getElementById( 'btn' ).onclick = function(){
let a = CreateModal()
a.style.display = 'block';
};
</script>
如上在功能方面我们是完成了一个惰性的单例,但是从代码优化角度上看,是不优雅的,我们把创建和管理都放在了一个方法里,这样的代码违反了“单一职责”,接下来分开处理下:
let CreateModal = function (html) {
this.html = html;
this.init();
};
CreateModal.prototype.init = function () {
let div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
};
let ProxySingletonCreateModal = (function () {
let divObj;
return function (html) {
if (!divObj) {
divObj = new CreateModal(html);
}
return divObj;
}
})();
let a = new ProxySingletonCreateModal('value1');
let b = new ProxySingletonCreateModal('value2');
alert(a === b); //true
CreateModal只是一个普通类,负责构造对象,负责管理单例的逻辑移到了代理类ProxySingletonCreateModal中。
这样的组合写法达到了单例模式的效果,但是还没有达到精髓,我们可能在其他场景下需要的是创建唯一的iframe,script等,就需要把这些代码抄写一遍。在《JavaScript设计模式与开发实践》一书中,给出了高度抽象的单例模式代码,贴出来看下
var getSingle = function( fn ){
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
};
形参fn是我们的构造函数,只要调用时传入需要的构造函数,就可以生成一个新的单例。
以上便是关于单例模式的一些认识,如有不正确之处,欢迎批评指正