单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建定义:
保证一个类仅有一个实例,并提供一个访问它的全局访问点
核心:
确保只有一个实例,并提供全局访问
实现单例模式
要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象var Singleton = function( name ){
this.name = name;
// 实例
this.instance = null;
};
Singleton.prototype.getName = function(){
console.log(this.name)
};
Singleton.getInstance = function( name ){
// 如果存在实例,则直接跳过
if ( !this.instance ){
this.instance = new Singleton( name );
}
return this.instance;
};
var a = Singleton.getInstance( 'sven1' );
var b = Singleton.getInstance( 'sven2' );
console.log( a === b ); // true
a.getName(); // sven1
b.getName(); // sven1
或者
var Singleton = function( name ){
this.name = name;
};
Singleton.prototype.getName = function(){
console.log(this.name)
};
// 使用闭包来保存实例对象
Singleton.getInstance = (function(){
var instance = null;
return function( name ){
if ( !instance ){
instance = new Singleton( name );
}
return instance;
}
})();
console.log( a === b ); // true
a.getName(); // sven1
b.getName(); // sven1
我们通过 Singleton.getInstance 来获取 Singleton 类的唯一对象,这种方式存在一个问题,就是增加了这个类的“不透明性”,Singleton 类的使用者必须知道这是一个单例类, 必须使用 Singleton.getInstance 来获取对象,不能通过以往new来获取对象
用代理实现单例模式
// 构造函数
var CreateSingleton = function( name ){
this.name = name;
this.init();
}
CreateSingleton.prototype.init = function(){
// 这里执行初始化方法
console.log( this.name )
}
/**
* 引入代理类-proxyClass来负责管理单例
* 这里使用了自执行的匿名函数和闭包,将变量封装在闭包的内部
*/
var proxyClass = (function(){
var instance;
return function( name ){
// 如果存在实例,则跳过
if ( !instance ){
instance = new CreateSingleton( name );
}
return instance;
}
})();
var a = new proxyClass('singleton1');
var b = new proxyClass('singleton2');
console.log( a === b ); // true
通过引入代理,我们可以直接通过new来获取单例对象,并且把负责管理单例的逻辑转移到了代理类-proxyClass中
,这样一来CreateSingleton就变成了一个普通的类,它跟proxyClass组合起来可以达到单例模式的效果
JavaScript 中的单例模式
前面提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从 “类”中创建而来。在以类为中心的语言中,这是很自然的做法。比如在 Java 中,如果需要某个 对象,就必须先定义一个类,对象总是从类中创建而来的但JavaScript 其实是一门无类语言,在 JavaScript 中创建对象的方法非常简单,直接创建一个变量对象,反正我们只需要一个“唯一”的对象,没必要为它先创建一个“类”呢?这无异于穿棉衣洗澡
单例模式的核心是确保只有一个实例,并提供全局访问
var a = {};
之后我们就有点迷惑了,既然如此,那我们直接创建一个唯一的变量就是了,但是全局变量存在很多问题,它很容易造成命名空间污染,所以我们要用几种方式来降低全局变量带来的命名污染
① 使用命名空间
适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量var namespace = {
a: function(){
alert (1);
},
b: function(){
alert (2);
}
};
② 使用闭包封装私有变量
这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信// 我们用下划线来约定私有变量__name 和__age,它们被封装在闭包产生的作用域中,外部是访问不到这两个变量的,这就避免了对全局的命令污染
var user = (function(){
var __name = 'sven',
__age = 29;
return {
getUserInfo: function(){
return __name + '-' + __age;
}
}
})();
惰性单例
惰性单例指的是在需要的时候才创建对象实例
实际上在开头的代码中头就使用过这种技术, instance 实例对象总是在我们调用 Singleton.getInstance 的时候才被创建,而不是在页面加载好 的时候就创建,代码如下:
Singleton.getInstance = (function(){
var instance = null;
return function( name ){
if ( !instance ){
instance = new Singleton( name );
}
return instance;
}
})();
不过这是基于“类”的单例模式, JavaScript 中并不适用,接下来展示js中与全局变量结合实现惰性的单例
var getSingle = function(fn){
var result
return function(){
return result || ( result = fn.apply(this, arguments) )
}
}
var createLoginLayer = function(){
var div = document.createElement( 'div' )
div.innerHTML = '我是登录浮窗'
div.style.display = ' none '
document.body.appendChild( div )
return div
}
//预渲染浮窗
var LoginLayer = getSingle( createLoginLayer )
//打开浮窗
LoginLayer().style.display = 'block'
//关闭浮窗
LoginLayer().style.display = 'none'
但是现在都2020年了,都用框架了,已经很少有完全直接操作DOM的时候了,但是设计模式还是可以用的
Vue中使用惰性单例
惰性单例有两大优点:
因此在Vue中我们可以这样优化代码:单例 —— 只被创建一次
惰性 —— 只加载已“缓存好的”
①v-if主动控制首次不加载,需要时才加载
②v-show首先默认为true,只在v-if为ture之后再控制显示与隐藏
<template>
<div>
<el-button @click="isLoad = true">点击开始加载dialog</el-button>
<!-- element的对话框组件 -->
<el-dialog
:visible.sync="dialogVisible"
width="30%"
v-if="isLoad"
>
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false"
>确 定</el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
isLoad:false,
dialogVisible: false,
};
},
};
</script>
这里是通过点击将isLoad变为true才开始加载el-dialog的,el-dialog的隐藏和显示就是用v-show来控制,这种优化适用于一些占很大资源却对于用户不常有的功能,导致加载时间会被无意义地拖长的情况
Vuex中的单例模式
在项目中引入Vuex:// 引入vuex插件
import Vuex from 'vuex'
// 安装vuex插件
Vue.use(Vuex)
// 创建vuex的实例
var store = new Vuex.Store({})
// 将store注入到Vue实例中
new Vue({
el: '#app',
store
})
通过调用Vue.use()方法,安装 Vuex 插件。Vuex 插件是一个对象,它在内部实现了一个 install 方法,这个方法会在插件安装时被调用,从而把 Store 注入到Vue实例里去
let Vue // Vue的作用和上面的instance一样
...
export function install (_Vue) {
// 判断传入的Vue实例对象是否已经被install过(是否有了唯一的state)
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
// 若没有,则为这个Vue实例对象install一个唯一的Vuex
Vue = _Vue
// 将Vuex的初始化逻辑写进Vue的钩子函数里
applyMixin(Vue)
}
上面便是 Vuex 源码中单例模式的实现办法了。通过这种方式,可以保证一个 Vue 实例(即一个 Vue 应用)只会被 install 一次 Vuex 插件,所以每个 Vue 实例只会拥有一个全局的 Store
参考
JavaScript设计模式与开发实践--曾探JavaScript惰性单例与vue性能优化应用
JavaScript设计模式——单例模式
若有错误或者对于vue对于单例模式更好的应用请在评论区留言,最后感谢阅读!!!
设计模式是对语言不足的补充
—— Peter Norvig
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!