什么是单例模式
单例模式(Singleton):又称单体模式,是只允许实例化一次的对象类。有时我们也用一个对象类规划一个命名空间,井井有条地管理对象上的属性和方法。
单例模式的作用
作用1:定义命名空间
// 根据id获取元素
function get(id){
return document.getElementById(id)
}
// 简单样式属性设置
function css(id,key,value){
get(id).style[key] = value
}
// 元素值设置
function html(id,value){
get(id).innerHTML = value
}
// 元素方法设置
function on(id,type,fn){
get(id)['on' + type] = fn
}
可以看到,上面一段代码中,直接在页面中添加了很多变量,比如根据元素id获取元素方法get,如果之后别人要修改你的页面,增加代码时就很容易不小心重定义get变量或者重写了get方法。
所以我们可以使用单例模式进行修改:
var Zhou = {
get:function(id){
return document.getElementById(id)
},
css:function(id,key,value){
this.get(id).style[key] = value
},
html:function(id,value){
this.get(id).innerHTML = value
},
on:function(id,type,fn){
this.get(id)['on' + type] = fn
}
}
可以看到,当我们将所有的方法都放在了一个对象里,而这个对象我们就成为命名空间(namespace),也有人称之为名称空间。它解决了这么一类问题:为了让代码更容易懂,我们经常用单词或者拼音的方式定义变量或者方法。但是这样就会出现一个问题,不同人定义的变量使用的单词可能重复,此时就需要使用命名空间约束每个人定义的变量来解决这个问题。 比如小周写的代码,他就可以定义一个Zhou命名空间,这样以后小周定义的变量就可以使用Zhou.xx来使用。
注意:css等方法中使用到了get这个变量,而get方法是在Zhou对象中,因此需要通过Zhou.get进行使用,而对象中的this指向的就是当前对象,因此更通常使用this.get进行使用。
作用2:区分模块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var baidu = {
}
</script>
<script src="./dom.js"></script>
<script src="./event.js"></script>
<script>
console.log(baidu.event.endTrim(' dfafas '))
baidu.dom.creat('div','testDom')
</script>
</body>
</html>
结果:
dom.js文件:
baidu.dom = {
creat:function(tagName,id,parentNode){
var dom = document.createElement(tagName)
dom.setAttribute('id',id)
var parent = parentNode?parentNode:document.getElementsByTagName('body')[0]
parent.appendChild(dom)
},
addClass:function(id,className){
document.getElementById(id).classList.add(className)
},
removeClass:function(id,className){
document.getElementById(id).classList.remove(className)
}
}
event.js文件:
baidu.event = {
// 删除字符串末尾的空白字符,并用----用于标识区分
endTrim:function(str){
let index = null
for(let i = str.length - 1; i >= 0;i--){
if(str[i] !== ' '){
index = i
break
}
}
return str.slice(0,index) + '----'
}
}
注意,在使用baidu.dom或者baidu.event之前,需要先声明baidu这个变量。然后通过
<script src="./dom.js"></script>
<script src="./event.js"></script>
引入对应的模块。
小型代码库
除了使用script对不同的模块js进行引用,我们还可以直接使用单例模式进行一个小型代码库的创建。
var baidu = {
dom:{
creat:function(tagName,id,parentNode){
var dom = document.createElement(tagName)
dom.setAttribute('id',id)
var parent = parentNode?parentNode:document.getElementsByTagName('body')[0]
parent.appendChild(dom)
},
addClass:function(id,className){
document.getElementById(id).classList.add(className)
},
removeClass:function(id,className){
document.getElementById(id).classList.remove(className)
}
},
event:{
// 删除字符串末尾的空白字符,并用----用于标识区分
endTrim:function(str){
let index = null
for(let i = str.length - 1; i >= 0;i--){
if(str[i] !== ' '){
index = i
break
}
}
return str.slice(0,index) + '----'
}
}
}
console.log(baidu.event.endTrim(' dfafas '))
baidu.dom.creat('div','testDom')
无法修改的静态变量
我们知道在JavaScript中是没有static关键字的,虽然在es6中已经有了const可以对常量(静态变量)进行声明。但是我们仍然可以使用单例模式对静态变量进行模拟,了解静态变量的原理。
var Conf = (function(){
// 私有变量
var conf = {
MAX:100,
MIN:1,
SIZE:1000
}
return {
// 取值器
get:function(name){
return conf[name]?conf[name]:null;
}
}
})()
var count = Conf.get('SIZE')
console.log(count)
所谓静态变量,就是只能访问不能修改并且创建后就能使用的变量。在上面一段代码中,Conf.get('SIZE')
就是一个模拟的静态变量。我们通过在一个函数内部定义一个私有变量,并且通过一个特权方法进行访问,而没有提供对应的特权方法进行修改,那么就符合了静态变量的定义。最后将这个特权方法作为一个对象的属性return出去,这也是特权方法的一种应用方式。
惰性单例
在很多情况下,有的对象和方法根本不需要创建或者执行,或者创建/执行之后只需要沿用上次生成的结果即可。此时我们就可以用到单例模式。
var LazySingle = (function(){
// 单例实例引用
var _instance = null
// 单例
function Single(){
console.log('开始创建')
// 私有属性和方法
return {
publicProperty:'1',
publicMethod:function(){
console.log(1111)
}
}
}
// 获取单例对象
return function(){
// 如果为创建单例将创建单例
if(!_instance){
_instance = Single()
}
// 返回单例
return _instance
}
})()
// 测试
console.log(LazySingle().publicProperty)
console.log(LazySingle().publicProperty)
console.log(LazySingle().publicProperty)
要理解惰性单例,首先我们需要回顾下闭包的一些知识,在我们的第二章 面向对象编程之封装中的 闭包的用处 这一知识点中曾经说过,将私有变量通过内部函数return出去,就会使该私有变量一直存在内存中而不会被释放,从而达到隐藏变量但是又能操作的效果。
而在惰性单例这个例子中,很明显_instance就是这个私有变量,因为第一次执行的时候对它进行了赋值并且存在了内存中没有进行释放,所以在第二次进行判断时,就会直接return它本身。
需要注意的是,在第一次执行LazySingle()时,LazySingle的值就由自执行函数
(function(){
// 单例实例引用
var _instance = null
// 单例
function Single(){
console.log('开始创建')
// 私有属性和方法
return {
publicProperty:'1',
publicMethod:function(){
console.log(1111)
}
}
}
// 获取单例对象
return function(){
// 如果为创建单例将创建单例
if(!_instance){
_instance = Single()
}
// 返回单例
return _instance
}
})()
变成了return回来的函数
// 获取单例对象
function(){
// 如果为创建单例将创建单例
if(!_instance){
_instance = Single()
}
// 返回单例
return _instance
}
总结
单例模式提供了命名空间,区分了不同的模块,并且可以用于模拟静态变量,甚至可以用于延迟创建(对象,方法都可以)。
最出名的单例模式应该就是jQuery代码库了,它提供了一个变量jQuery(别名$),用于管理每个模块的代码。
下一章 外观模式