这是我参与更文挑战的第22天,活动详情查看:更文挑战
接上篇
设计模式|JavaScript实现单例模式(上篇)
JavaScipt中的单例模式
对于传统面向对象语言而言,单例对象是从”类”中创建而来的,比如Java中,如果需要某个对象,就必须先定义一个类,对象从类中被创建出来。
但是对于JavaScript而言,其实是一门无类(class-free)语言,因此在JavaScript中创建对象的方法特别简单,只需要保证单例模式的核心——确保只有一个实例,并提供全局访问。
那么他就是一个单例类。
直接在全局上下文定义一个对象字面量的全局变量,也可以把它看做单例类,但是全局变量很容易造成命名空间污染的问题。
// 全局上下文的a对象,它也可以看做是一个单例类。
let a = {};
复制代码
模拟命名空间,可以减少全局变量造成污染的概率。
/**
* 对象字面量的方法模拟命名空间
*/
let namespace1 = {
a: function(){
console.log(1);
},
b: function(){
console.log(2);
}
};
/**
* 动态创建命名空间
*/
let MyApp = {};
MyApp.namespace = function( name ){
var parts = name.split( '.' );
var current = MyApp;
for ( var i in parts ){
if ( !current[ parts[ i ] ] ){
current[ parts[ i ] ] = {};
}
current = current[ parts[ i ] ];
}
};
MyApp.namespace( 'event' );
MyApp.namespace( 'dom.style' );
console.dir( MyApp );
// 上述代码等价于:
let MyApp = {
event: {},
dom: {
style: {}
}
};
/**
* 使用闭包封装私有变量
*/
let user = (function(){
let __name = 'sven',
__age = 29;
return {
getUserInfo: function(){
return __name + '-' + __age;
}
}
})();
复制代码
在ES6中,我们可以模块(module)来模拟命名空间。
- 创建一个模块文件
// 模块文件
// MyApp.js
let MyApp = {
event: {},
dom: {
style: {}
}
};
export default MyApp;
复制代码
- 在页面中引入模块
<script type="module" src="./MyApp.js"></script>
复制代码
- 在需要使用该模块的js文件中导入模块并使用
import MyApp from './MyApp.js';
console.log(MyApp)
复制代码
懒汉式和饿汉式
单例模式的写法有很多,懒汉式,饿汉式,注册式,序列化式等等……
这里着重对懒汉式单例和饿汉式单例做一个比较讲解。
- 懒汉式:默认不会实例化,什么时候用到了,才会创建对象实例。
- 饿汉式:类加载的时候就会进行实例化,并且创建单例对象实例。
像先前的例子,Singleton.getInstance在调用的时候才会被创建,这种就是典型的懒汉式单例。
Singleton.getInstance = (function(){
var instance = null;
return function( name ){
if ( !instance ){
instance = new Singleton( name );
}
return instance;
}
})();
复制代码
用在实际的例子,比如创建一个登录弹框,我们在JavaScript也可以这样做。
var createLoginLayer = (function(){
var div;
return function(){
if ( !div ){
div = document.createElement( 'div' );
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild( div );
}
return div;
}
})();
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
};
复制代码
通用的懒汉式单例
上面的登录弹框写法虽然可用,但是违反了设计模式的”单一职责原则”,创建对象和管理单例的逻辑都耦合在了一起。如果之后要创建更多的单例,就要重复复制粘贴一遍又一遍单例代码。
所以我们需要把管理单例的逻辑从原来的代码中抽离出来。
let getSingle = function( fn ){
let result; // 保存在闭包中,不会被销毁。
return function(){
return result || ( result = fn.apply(this, arguments ) );
}
};
复制代码
接着,无论有多少种不同的创建实例对象的方法,getSingle这个管理管理方法都能很好的完成任务。
/**
* 例子1:创建登录弹窗,保证全局只有一个登录弹窗
*/
// 创建登录弹窗的方法
let createLoginLayer = function(){
var loginDiv = document.createElement( 'div' );
loginDiv.innerHTML = '我是登录浮窗';
loginDiv.style.display = 'none';
document.body.appendChild( loginDiv );
return loginDiv;
};
// 获取创建登录弹窗方法的单例
var createSingleLoginLayer = getSingle( createLoginLayer );
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleLoginLayer(); // 使用这个单例
loginLayer.style.display = 'block';
};
/**
** 例子2:创建iframe,保证全局只有一个iframe
*/
// 创建iframe的单例方法
let createSingleIframe = getSingle( function(){
let iframe = document.createElement ('iframe');
document.body.appendChild(iframe);
return iframe;
});
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleIframe(); // 使用这个单例
loginLayer.src = 'http://baidu.com';
};
/**
** 例子3:给列表绑定click事件,保证列表只在初始化后执行一次事件绑定
*/
// 绑定点击事件的方法
let bindEvent = getSingle(function(){
document.getElementById( 'div1' ).onclick = function(){
console.log ( 'click' );
}
return true;
});
var render = function(){
console.log( '开始渲染列表' );
bindEvent();
};
// 虽然执行了3次render,但是绑定事件的操作只执行了一次。
render();
render();
render();
复制代码
参考资料
《JavaScript设计模式与开发实践》——单例模式
Proxy代理实现单例模式
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END