JavaScript设计模式之单例模式
本文由我们团队[肖建朋]总结
学习资料主要来源于《JavaScript设计模式与开发实践》一书
JavaScript基础知识简单介绍
JavaScript 是一种动态类型语言,当我们对一个变量赋值时,不需要考虑它的类型。动态类型一个原则:【面向接口编程,而不是面向实现编程】
JavaScript 没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象
与对象之间的继承。JavaScript 也没有在语言层面提供对抽象类和接口的支持
this
this的指向
-
作为对象的方法调用。当函数作为对象的方法被调用时,this 指向该对象。
-
作为普通函数调用。当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this 总是指向全局对象。
-
构造器调用。JavaScript 中没有类,但是可以从构造器中创建对象,同时也提供了new 运算符,使得构造
器看起来更像一个类。当用new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this 就指向返回的这个对象。 -
Function.prototype.call 或Function.prototype.apply 调用。
跟普通的函数调用相比,用Function.prototype.call 或Function.prototype.apply 可以动态地改变传入函数的this:
call和apply的区别
apply接受两个参数,第一个参数指定了函数体对象的指向,第二个参数为一个带下apply this 标的集合,这个集合可以为数组,也可以为类数组,方法把这个集合中的元素作为参数传apply递给被调用的函数
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] );
call传入的参数数量不固定,相同的是,第一个参数也是代表函数体内指向,call apply this
从第二个参数开始往后,每个参数被依次传入函数
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.call( null, 1, 2, 3 );
闭包和高阶函数
变量的作用域
变量的作用域,就是指变量的有效范围。
我们最常谈到的是在函数中声明的变量作用域。当在函数中声明一个变量的时候,如果该变量前面没有带上关键字var,这个变量就会成为全局变量,这当然是一种容易造成命名冲突的做法。
另外一种情况是用var 关键字在函数中声明变量,这时候的变量即是局部变量,只有在该函数内部才能访问到这个变量,在函数外面是访问不到的
var a = 1;
var func1 = function(){
var b = 2;
var func2 = function(){
var c = 3;
alert ( b ); // 输出:2
alert ( a ); // 输出:1
}
func2();
alert ( c ); // 输出:Uncaught ReferenceError: c is not defined
};
func1();
变量的生存周期
对于全局变量来说,全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量。
而对于在函数内用var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,
它们都会随着函数调用的结束而被销毁:
代码示例:
var func = function(){
var a = 1; // 退出函数后局部变量a 将被销毁
alert ( a );
};
func();
var func = function(){
var a = 1;
return function(){
a++;
alert ( a );
}
};
var f = func();
f(); // 输出:2
f(); // 输出:3
f(); // 输出:4
f(); // 输出:5
使用闭包使for 循环中使用异步方法
<html>
<body>
<div style="width: 100; height: 100;" >1</div>
<div style="width: 100; height: 100;">2</div>
<div style="width: 100; height: 100;">3</div>
<div style="width: 100; height: 100;">4</div>
<div style="width: 100; height: 100;"> 5</div>
<script>
var nodes = document.getElementsByTagName( 'div' );
for ( var i = 0, len = nodes.length; i < len; i++ ){
((i) => {
nodes[ i ].onclick = function(){
alert ( i );
}
})(i)
}
</script>
</body>
</html>
闭包的作用
- 封装变量
- 延续局部变量的寿命
高阶函数
高阶函数至少满足以下一种条件:
- 函数可以作为参数传递
- 函数可以作为返回值输出
在后面的单例模式会有具体示例
单例模式
定义
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。要实现单例模式,要用一个变量来标志当前是否为某个类创建了对象,若是,则在下次获取该类的实例时,直接返回之前创建的对象。
类图
简单实现单例模式
<!DOCTYPE html>
<html>
<body>
<p>
简单的单例模式
</p>
<script>
var Singleton = function (name) {
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function () {
alert(this.name);
};
Singleton.getInstance = function (name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
};
var a = Singleton.getInstance('sven1');
a.getName()
var b = Singleton.getInstance('sven2');
b.getName()
alert(a === b); // true
</script>
<p>
</p>
</body>
</html>
代理实现代理模式
在CreateDiv 构造函数中,把负责管理单例的代码移除出去,使它成为一个普通的创建div 的类。
把负责管理单例的逻辑移到了代理类proxySingletonCreateDiv 中。
<!DOCTYPE html>
<html>
<body>
<p>
用代理实现单例模式
</p>
<script>
// 创建Div的类
var CreateDiv = function(html) {
this.html = html
this.init()
}
// CreateDiv的初始化方法
CreateDiv.prototype.init = function() {
var div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
// 引入代理类
var ProxySingletonCreateDiv = (function() {
// 这里使用了一个闭包结构
var instance
return function(html) {
if(!instance) {
instance = new CreateDiv(html)
}
return instance
}
})();
var a = new ProxySingletonCreateDiv('sevnt1')
console.log(a)
var b = new ProxySingletonCreateDiv('sevnt2')
console.log(b)
alert(a===b)
var c = new CreateDiv('seven3')
console.log(c)
</script>
</body>
</html>
###JavaScript 中的单例模式
在JavaScript 中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它先创建一个“类”呢?这无异于穿棉衣洗澡,传统的单例模式实现在JavaScript 中并不适用。单例模式的核心是确保只有一个实例,并提供全局访问。
全局变量不是单例模式,但在JavaScript中我们经常把全局变量当做单例模式来使用。例如:
var a = {} //加入a被声明在全局作用域下,则a可以在代码任何地方被使用,a就满足了单例模式的条件
但是全局变量容易造成命名污染,所以在JavaScript中要尽量少使用全局变量。
降低全局变量命名污染的方法:
- 使用命名空间 ,减少变量和全局作用域打交道的机会
var MyApp = {
event: {},
dom: {
style: {}
}
};
或动态创建命名空间
var 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 ] ];11
}
};
MyApp.namespace( 'event' );
MyApp.namespace( 'dom.style' );
console.dir( MyApp );
-
使用闭包封装变量,只暴露一些接口和外界通信。例如:
用下划线来约定私有变量name 和age,它们被封装在闭包产生的作用域中,外部是访问不到这两个变量的,这就避免了对全局的命令污染。
var user = (function(){ var __name = 'sven', __age = 29; return { getUserInfo: function(){ return __name + '-' + __age; } } })();
惰性单例
惰性单例指的是在需要的时候才创建对象实例。当点击按钮时显示“登录浮窗“
通用惰性单例时序图:
以创建一个loginDiv为例,
第一次调用createSingleLoginLayer,依次调用getSingle、createLoginLayer成功创建loginDiv;
第二次调用createSingleLoginLayer,调用getSingle,getSingle中判断得知loginDiv已存在,于是返回已存在的loginDiv。
通用惰性单例实例 :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
<script src="main.js"></script>
</head>
<body>
<button id="loginBtn">登录</button>
<button id="iframeBtn">打开百度</button>
<br>
</body>
<script>
// 单例管理函数
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'
div.style.backgroundColor = 'blue'
div.style.width = '100px'
div.style.height='100px'
document.body.appendChild(div)
return div
}
var createSingleLoginLayer = getSingle(createLoginLayer)
var createSingleIframe = getSingle(function(){
var iframe = document.createElement('iframe')
iframe.style.width='100%'
iframe.style.height='600px'
document.body.appendChild(iframe)
return iframe
})
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'block'
}
document.getElementById( 'iframeBtn' ).onclick = function(){
var iframe = createSingleIframe()
iframe.src='http://baidu.com'
}
</script>
</html>
总结
单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个。更奇妙的是,创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威力。