单例模式
单例模式,从名字拆分来看,单指的是一个,例是实例,意思是说多次通过某个类创造出来实例始终只返回同一个实例,它限制一个类只能有一个实例。单例模式主要是为了解决对象的创建问题。单例模式的特点:
- 一个类只有一个实例
- 对外提供唯一的访问接口
在一些以类为核心的语言中,例如java,每创建一个对象就必须先定义一个类,对象是从类创建而来。js是一门无类(class-free)的语言,在js中创建对象的方法非常简单,不需要先定义类即可创建对象。
在js中,单例模式是一种常见的模式,例如浏览器中提供的window对象,处理数字的Math对象。
单例模式的实现
1. 对象字面量
在js中实现单例最简单的方式是创建对象字面量,字面量对象中可以包含多个属性和方法。
1 | var mySingleton = { |
以上创建一个对象,放在全局中,就可以在任何地方访问,要访问对象中的属性和方法,必须通过mySingleton这个对象,也就是说提供了唯一一个访问接口。
2.使用闭包私有化
扩展mySingleton对象,添加私有的属性和方法,使用闭包的形式在其内部封装变量和函数声明,只暴露公共成员和方法。
1 | var mySingleton = (function (){ |
把privateVal和privateVal被封装在闭包产生的作用域中,外界访问不到这两个变量,这避免了对全局命名污染。
3.惰性单例
无论使用对象字面量或者闭包私有化的方式创建单例,都是在脚本一加载就被创建。有时候页面可能不会用到这个单例对象,这样就会造成资源浪费。对于这种情况,最佳处理方式是使用惰性单例,也就是在需要这个单例对象时再初始化。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
30
31
32
33
34
35
36
37
38var mySingleton = (function (){
function init(){
//私有变量
var privateVal = '我是私有变量';
//私有函数
function privateFunc(){
console.log('我是私有函数');
}
return {
attr1:1,
attr2:2,
method(){
console.log("method");
privateFunc();
}
}
}
//用来保存创建的单例对象
var instance = null;
return {
getInstance (){
//instance没有存值,就执行函数得到对象
if(!instance){
instance = init();
}
//instance存了值,就返回这个对象
return instance;
}
}
})();
//得到单例对象
var singletonObj1 = mySingleton.getInstance();
var singletonObj2 = mySingleton.getInstance();
console.log( singletonObj1 === singletonObj2 ); //true
程序执行后,将创建单例对象的代码封装到init函数中,只暴露了获取单例对象的函数getInstance。当有需要用到时,通过调用函数mySingleton.getInstance()得到单例对象,同时使用instance将对象缓存起来,再次调用mySingleton.getInstance()后得到的是同一个对象,这样通过一个函数不会创建多个对象,起到节省资源的目的。
4.使用构造函数
可以使用构造函数的方式,创造单例对象:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function mySingleton(){
//如果缓存了实例,则直接返回
if (mySingleton.instance) {
return mySingleton.instance;
}
//当第一次实例化时,先缓存实例
mySingleton.instance = this;
}
mySingleton.prototype.otherFunc = function (){
console.log("原型上其他方法");
}
var p1 = new mySingleton();
var p2 = new mySingleton();
console.log( p1 === p2 ); //true
当第一次使用new调用函数创建实例时,通过函数的静态属性mySingleton.instance把实例缓存起来,在第二次用new调用函数,判断实例已经缓存过了,直接返回,那么第一次得到的实例p1和第二次得到的实例p2是同一个对象。这样符合单例模式的特点:一个类只能有一个实例。
这样做有一个问题,暴露了可以访问缓存实例的属性mySingleton.instance,这个属性的值可以被改变:
1 | var p1 = new mySingleton(); |
改变了mySingleton.instance值后,再通过new调用构造函数创建实例时,又会重新创建新的对象,那么p1和p2就不是同一个对象,违反了单例模式一个类只能有一个实例。
单例模式登录框
使用面向对象实现一个登录框,在点击登录按钮后登录框被append到页面中,点击关闭就将登录框从页面中remove掉,这样频繁的操作DOM不合理也不是必要的。
只需要在点击关闭时隐藏登录框,再次点击按钮后,只需要show出来即可。
页面中只放一个按钮:1
<input type="button" value="登录" id="loginBtn" />
js实现:
1 | function Login(){ |