定义:保证类仅有一个实例,并提供一个访问它的全局访问点
1. 实现单例模式
用一个变量标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,返回之前创建的对象。
//实现单例模式
var SingleTon = function(name) {
this.name = name;
this.instance = null;
};
SingleTon.prototype.getSingelName = function() {
console.log(this.name);
};
SingleTon.getInstance = function(name) {
if(!this.instance) {
this.instance = new SingleTon(name);
}
return this.instance;
}
var a = SingleTon.getInstance('tom');
var b = SingleTon.getInstance('jerry');
console.log(a === b);
// 另一种实现方式
var SingleTon = function(name) {
this.name = name;
};
SingleTon.prototype.getSingelName = function() {
console.log(this.name);
};
SingleTon.getInstance = (function() {
var instance = null;
return function(name) {
if (!instance) {
instance = new SingleTon(name);
}
return instance;
}
})();
var a = SingleTon.getInstance('tom');
var b = SingleTon.getInstance('jerry');
console.log(a === b);
//缺点:增加了类的不透明性,使用者必须知道这是一个单例类,跟以往new xx创建对象不同,要通过调用getInstance获取对象
2. 透明的单例模式
//透明单例模式
var CreatDiv = (function() {
var instance = null;
var CreatDiv = function(html) {
if (instance) {
return instance;
}
this.html = html;
this.init();
return instance = this;
/* new运算符调用函数时,该函数总会返回一个对象,构造器的this指向返回的对象,即a=new CreatDiv('tom')时,this指向a*/
};
CreatDiv.prototype.init = function() {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.append(div);
}
return CreatDiv;
})();//自执行函数立即执行,此时外层CreatDiv是内部返回的函数CreatDiv
console.log(CreatDiv);
var a = new CreatDiv('tom');//new运算符调用外层CreatDiv,实际是调用的内部返回的CreatDiv函数
var b = new CreatDiv('jerry');
console.log(b === a);
//缺点: CreatDiv构造函数实际负责两件事,第一创建对象和初始init方法,第二是保证只有一个对象。不符合“单一职责原则”
3. 用代理实现单例模式
//用代理实现单例
var CreatDiv = function(html) {
this.html = html;
this.init();
};
CreatDiv.prototype.init = function() {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.append(div);
};
var proxySingletonCreatDive = (function() {
var instance = null;
return function(html) {
if (!instance) {
instance = new CreatDiv(html);
}
return instance;
}
})();
var a = new proxySingletonCreatDive('newTom');
var b = new proxySingletonCreatDive('newJerry');
console.log(a === b);
4. JS中的单例模式
上面的例子更接近传统面向对象语言的实现,单例对象从类中而来,如在JAVA,如果需要一个对象,则要先建一个类,对象总是从类中创建的;而JS是一种无类(class-free)语言,我们需要创建一个对象,不需要先创建一个类,传统的单例模式实现在JS中并不适用。
单例模式的核心是确保只有一个实例,并提供全局访问
全局变量不是单例模式,但在JS开发中,经常被当做单例使用。
但全局变量容易造成命名污染。解决方法:
- 使用命名空间
最简单的是使用对象字面量的方法,并不会杜绝全局变量,但可以减少全局变量的使用
var namespace = {
a: function() {},
b: function() {}
};
//动态创建命名空间
var myApp = {};
myApp.nameSpaceFun = function(params) {
var ary = params.split('.');
var current = myApp;
for (var i = 0; i < ary.length; i++) {
if (!current[ary[i]]) {
current[ary[i]] = {};
}
current = current[ary[i]];
}
}
myApp.nameSpaceFun('id');
myApp.nameSpaceFun('dom.style');
console.log(myApp);
- 使用闭包封装私有变量
5. 惰性单例
指在需要时才创建对象实例。
//惰性加载单例,以创建webQQ登录为例
var creatLoginLayer = (function() {
var div;
return function() {
if (!div) {
div = document.createElement('div');
div.innerHTML = 'login';
div.style.display = 'none';
document.body.appendChild(div);
}
return div;
}
})();
var btn = document.getElementById('loginBtn');
btn.onclick = function() {
var loginLayer = creatLoginLayer();
loginLayer.style.display = 'block';
}
//存在问题:违反单一原则,创建对象逻辑和管理单例逻辑没有分开;管理单例逻辑无法复用
6. 通用惰性单例
var getSingleton = function(fn) {
var result;
return function() {
return result || ( result = fn.apply(this, arguments) );
}
};
var creatDiv = function() {
var div = document.createElement('div');
div.innerHTML = 'login';
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
var creatdivLayer = getSingleton(creatDiv);
var btn = document.getElementById('loginBtn');
btn.onclick = function() {
var loginLayer = creatdivLayer();
loginLayer.style.display = 'block';
}