单例模式属于创建模式的一种。通过单例模式创建的类只有一个实例。这种模式使得类的对象成为系统的唯一实例。而我们在系统中多次调用这个类的实例的时候,并不会去覆盖这个类,也可以我们在开发的过程中避免变量覆盖。
它的核心概念:限制类的实例化次数并且只能实例化一次,一个类只能存在一个实例,并提供一个全局的访问点。
举个例子,我们在系统中通过字面量去定一个对象 User,如果有其它的开发者去重复定义这对象的时候,就会把之前的对象进行覆盖,从而导致这个对象不能正确的使用,并还有一定耦合性。
var User = {
name:"黑黑的脸蛋",
age:"23"
}
如果我们用单例模式对这个对象进行封装,并让这个对象成为系统中的唯一对象,就不会出现变量覆盖的问题了。单例模式可以根据复杂度分为 简单版单例,透明版单例,代理单例以及惰性单例
简单单例
我们可以利用函数是系统中的一等公民定义,在函数属性下创建一个方法来管控单例创建出来的实例,让创建出来的实例作为唯一的实例。
function Singleton( name,age ) {
this.name = name
this.age = age
// 管控单例创建的实例
this.instance = null
}
Singleton.prototype.getUser = function(){
return {
name:this.name,
age:this.age
}
}
Singleton.getInstance = function( name,age ) {
if( !this.instance ) {
this.instance = new Singleton( name,age )
}
return this.instance
}
var SingletonUser = Singleton.getInstance( "黑黑的脸蛋","23" )
可以看出以上的代码中,我们在单例下定义了一个 getInstance 方法来管控单例,并在函数内部创建了一个 this.instance 属性收集创建出来的实例,让第一次创建出来的实例作为系统的唯一实例。
可以看到,我无论调用多少次 getInstance,它第一次创建的实例都不会有所变化。这就是单例模式中的限制类的实例化次数并且只能实例化一次,一个类只能存在一个实例 从而也解决了对象覆盖的问题。
但是简单单例依旧存在问题,就是不够透明。我们需要调用 getInstance 来创建单例,导致创建的过程太过复杂。而我们需要通过 new 的方法来创建实例,把过程进一步简单化。
透明单例
var Singleton = ( function() {
var instance
return function ( name,age ) {
this.name = name
this.age = age
if( !instance ) {
instance = this
}
return instance
}
} )()
var SingletonUser = new Singleton("黑黑的脸蛋","23")
透明单例解决了简单版单例的创建实例的过程复杂度,并可以直接使用 new 操作符来直接创建实例。
但是透明单例依旧没有解决代码的耦合性,创建实例和管控实例代码糅杂在一起,不符合设计模式中的 "单一职责原则"。
代理单例
通过“代理”的形式,意图解决:将管理单例操作,与对象创建操作进行拆分,实现更小的粒度划分,符合“单一职责原则”
var Singleton = ( function() {
var instance
function User( name,age ) {
this.name = name
this.age = age
}
User.prototype.getName = function() {
console.log( this.name );
}
return function ( name,age ) {
if( !instance ) {
instance = new User( name,age )
}
return instance
}
} )()
var SingletonUser = new Singleton("黑黑的脸蛋","23")
惰性单例
惰性单例,意图解决:需要时才创建类实例对象。在考虑优化场景的时候,我们往往会去想到懒加载解决方案,从而去实现 “按需加载”,而惰性单例也是解决这种优化场景的问题。
var GetSingleton = function( fn ) {
var instance
return function() {
if( !instance ) {
instance = fn.apply( this,arguments )
}
return instance
}
}
var createMessageAlert = function( message ) {
var alertBox = document.createElement( "div" )
alertBox.innerHTML = message
alertBox.style.display = "none"
alertBox.className = "login-alert"
document.body.appendChild( alertBox )
return alertBox
}
var createMessageAlert = GetSingleton( createMessageAlert )
document.getElementsByTagName("button")[0].addEventListener( "click",function() {
// 点击多次只会产生一个弹窗
var alertMessage = createMessageAlert("欢迎回来")
alertMessage.style.display = "block"
} )
在上例的代码中,我们定义了 GetSingleton 方法来实现惰性单例的创建,并在调用的过程中只会操控它唯一的实例。如果还需要更多的实例,可以继续调用 GetSingleton 来产生对应的实例方法。
ES6 实现简单版单例
class Singleton {
constructor( name,age ) {
this.name = name
this.age = age
}
static getInstance( name,age ) {
if( !this.instance ) {
this.instance = new Singleton( name,age )
}
return this.instance
}
}
var SingletonUser = Singleton.getInstance( "黑黑的脸蛋","23" )
ES6 实现透明单例
class Singleton {
constructor( name,age ) {
this.name = name
this.age = age
if( !Singleton.instance ) {
Singleton.instance = this
}
return Singleton.instance
}
}
var SingletonUser = new Singleton( "黑黑的脸蛋","23" )
ES6 实现代理单例
class Singleton {
constructor( name,age ) {
if( !Singleton.instance ) {
Singleton.instance = new User( name,age )
}
return Singleton.instance
}
}
function User( name,age ) {
this.name = name
this.age = age
}
User.prototype.getName = function() {
console.log( this.name );
}
var SingletonUser = new Singleton( "黑黑的脸蛋","23" )
ES6 实现惰性单例
class Singleton {
constructor( fn ) {
return function () {
if( !Singleton.instance ) {
Singleton.instance = fn.apply( this,arguments )
}
return Singleton.instance
}
}
}
var createMessageAlert = function( message ) {
var alertBox = document.createElement( "div" )
alertBox.innerHTML = message
alertBox.style.display = "none"
alertBox.className = "login-alert"
document.body.appendChild( alertBox )
return alertBox
}
var createMessageAlert = new Singleton( createMessageAlert )
document.getElementsByTagName("button")[0].addEventListener( "click",function() {
// 点击多次只会产生一个弹窗
var alertMessage = createMessageAlert("欢迎回来")
alertMessage.style.display = "block"
} )
场景应用
单例模式可以解决维护一个全局实例实例对象,避免这个对象被其他代码覆盖。
- 引用第三方库 ( 如 JQuery )
- 弹窗
- 全局状态管理器
store( Vuex / Redux )
项目中引入第三方库时,重复多次加载库文件时,全局只会实例化一个库对象,如 jQuery,lodash,moment。。。,其实它们的实现理念也是单例模式应用的一种:
// 引入代码库 libs(库别名)
if (window.libs != null) {
return window.libs; // 直接返回
} else {
window.libs = '...'; // 初始化
}
Vuex 中实例创建
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
function install (_Vue) {
if (Vue && _Vue === Vue) {
console.error('[vuex] already installed. Vue.use(Vuex) should be called only once.')
return
}
Vue = _Vue
}
总结
单例模式是创建型模式其中之一,这种模式可以让一个对象成为系统中的唯一实例,并且自身可以去自行创建。如果当这个实例创建完成,只能把这个实例作为它的唯一实例,当你去多次调用创建实例的过程中,他会“阻止”你想要再次生成的实例。而使得这个实例成为系统中的静态方法(类方法),所以你多次调用创建实例,会让这个过程变得毫无意义。