单例模式

在JavaScript中,单例(Singleton)模式是最基本又最有用的模式之一。这种模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一的变量进行访问。确保单例对象只有一份实例,你就可以确信自己的所有代码使用的都是同样的全局资源。

单例类在JavaScript中用途广泛:

(1)可以用来划分命名空间,以减少网页中全局变量的数量;

(2)可以在一种名为分支的技术中用来封装浏览器之间的差异;

(3)可以借助于单例模式,将代码组织得更为一致,从而使代码更容易阅读和维护。

单例的基本结构

最基本的单例实际上是一个对象字面值,它将一批有一定关联的方法和属性组织在一起。例如如下JavaScript代码:

var Singleton = {
attribute1: true;
attribute2: 10 method1: function() { },
method2: function() { }
};

这些成员可以通过Singleton加圆点运算符来访问:

Singleton.attribute1 = false;
var total = Singleton. attribute2 + 5;
var result = Singleton.method1();

 

对象字面值只是用以创建单例的方法之一,后面介绍的那些方法所创建的单体看起来更像其他面向对象语言中的单例类。另外,并非所有对象字面值都是单体,如果它只是用来模仿关联数组或容纳数据的话,那显然不是单例。但如果它是用来组织一批相关方法和属性的话,那就可能是单例,其区别主要在于设计者的意图。

创建拥有私有成员的单例
  使用下划线表示法
  在单例对象内创建类的私有成员的最简单、最直截了当的方法是使用下划线表示法(在JavaScript业界,如果变量和方法是使用下划线,则表示该变量和方法是私有方法,只允许内部调用,第三方不应该去调用)。参考实例如下:

GiantCorp.DataParser = {
// 私有方法
_stripWhitespace: function(str) {
return str.replace(/\s+/, '');
},
_stringSplit: function(str, delimiter) {
return str.split(delimiter);
},
// 公用方法
stringToArray: function(str, delimiter, stripWS) {
if (stripWS) {
str = this._stripWhitespace(str);
} var outputArray = this._stringSplit(str, delimiter);
return outputArray;
}
};

  

在如上代码中,stringToArray方法中使用this访问单体中的其他方法,这是访问单体中其他成员或方法的最简便的方法。但这样做有一点风险,因为this并不一定指向GiantCorp.DataParser例如,如果把某个方法用作事件监听器,那么其中的this指向的是window对象,因此大多数JavaScript库都会为事件关联进行作用域校正,例如在这里使用GiantCorp.DataParser比使用this更为安全。

使用闭包
  在单例对象中创建私有成员的第二种方法是借助闭包。因为单例只会被实例化一次,所以不必担心自己在构造函数中声明了多少成员。每个方法和属性都只会被创建一次,所以可以把它们声明在构造函数内部(因此也就位于同一个闭包中)。

使用闭包创建拥有私有成员的单例类的实例如下:

MyNamespace.Ssingleton = (function() {
// 私有成员
var privateAttribute1 = false;
var privateAttribute2 = [1, 2, 3]; function privateMethod1() { }
function privateMethod2() { } return {
// 公有成员
publicAttribute1: true;
publicAttribute2: 10, publicMethod1: function() { },
publicMethod2: function() { }
};
})();

  

这种单例模式又称为模块模式,指的是它可以把一批相关方法和属性组织为模块并起到划分命名空间的作用。 使用该种方式改造上面的实例,参考代码如下:

 GiantCorp.DataParser = (function() {
var whiteSpaceRegex = /\s+/;
// 私有方法
function stripWhitespace(str) {
return str.replace(whiteSpaceRegex, '');
}
function stringSplit(str, delimiter) {
return str.split(delimiter);
},
return {
// 公用方法
stringToArray: function(str, delimiter, stripWS) {
if (stripWS) {
str = stripWhitespace(str);
} var outputArray = stringSplit(str, delimiter);
return outputArray;
}
};
})();

将私有成员放在闭包中可以确保其不会在单例对象之外被使用,因此开发人员可以自由的改变对象的实现细节,而不会殃及别人的代码。还可以使用这种办法对数据进行保护和封装。

在JavaScript中实现“懒汉式”单例模式
  在如上的代码中,单例对象都是在脚本加载时被创建出来。对于资源密集型或配置开销甚大的单例,更合理的是使用“懒汉式”单例实现。这种实现方式的特别之处在于,对它们的访问必须借助于一个静态方法,例如调用单例类的getInstance()方法获得对象实例。

参考实现代码如下:

MyNamespace.Singleton = (function() {
// 定义一个私有的属性,该属性用于储存单例对象
var uniqueInstance;
function constructor() {
// 将单态操作放在这里
}
return {
getInstance: function() {
if (!uniqueInstance) {
uniqueInstance = constructor();
} return uniqueInstance;
}
}
})();

将一个单例转换为懒汉式加载方式后,必须对调用它的代码进行修改,例如之前调用:

MyNamespace.Singleton.publicMethod1();

应该改成如下代码:

MyNamespace.Singleton.getInstance().publicMethod1(); 

如果觉得命名空间名称太长,可以创建一个别名来简化它。

使用单例模式实现分支

分支是一种用来把浏览器之间的差异封装到运行期间进行设置的动态方法中的技术。例如,假设我们需要一个创建XHR对象的方法,这种XHR对象在大多数浏览器中是XMLHttpRequest对象的实例,但在IE早期版本中是某种ActiveX类的实例,这样一种方法通常会进行某种浏览器嗅探或对象探测。如果不用分支技术,那么每次调用这个方法时,所有这些浏览器嗅探代码都要再次运行。如果该方法调用频繁,将会严重影响效率。

要实现获取不同浏览器的XHR对象的功能,参考实现代码的实现步骤如下:

(1)判断有多少个分支(有3个),这些分支按其返回的XHR对象类型命名,这三个分支都有一个名为createXhrObject()的方法,该方法返回一个可以执行异步请求的新对象;

(2)根据条件将3个分支中某一分支的对象赋给那个变量,具体做法是逐一尝试XHR对象,直到遇到一个当前JavaScript环境所支持的对象为止。

参考代码如下所示:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>使用单例模式实现JavaScript中的分支</title>
<script type="text/javascript"> var SimpleXhrFactory = (function() {
// 三个分支
var standard = {
createXhrObject: function() {
alert("standard createXhrObject");
return new XMLHttpRequest();
}
};
var activeXNew = {
createXhrObject: function() {
alert("activeXNew createXhrObject");
return new ActiveXObject("Msxml2.XMLHTTP");
}
};
var activeXOld = {
createXhrObject: function() {
alert("activeXOld createXhrObject");
return new ActiveXObject("Microsoft.XMLHTTP");
}
};
// 为分支赋值
var testObject;
try {
testObject = standard.createXhrObject();
return standard;
} catch(e) {
try {
testObject = activeXNew.createXhrObject();
return activeXNew;
} catch(e) {
try {
testObject = activeXOld.createXhrObject();
return activeXOld;
} catch(e) {
throw new Error("在该浏览器环境中没有发现XHR对象.");
}
}
}
})();
SimpleXhrFactory.createXhrObject(); </script></head><body></body> 

用了分支技术后,所有的那些特性嗅探代码都只会执行一次,而不是没生成一个对象执行一次。这种技术适用于任何只有在运行时才能确定具体实现的情况。

单例模式的优缺点
  在JavaScript中使用单例模式的主要优点如下:

(1)对代码的组织作用:它将相关方法和属性组织在一个不会被多次实例话的单例中,可以使代码的调试和维护变得更轻松。描述性的命名空间还可以增强代码的自我说明性。将方法包裹在单例中,可以防止它们被其它程序员误改。

(2)单例模式的一些高级变体可以在开发周期的后期用于对脚本进行优化。

主要缺点如下:

(1)因为提供的是一种单点访问,所以它有可能导致模块间的强耦合。单体最好是留给定义命名空间和实现分支型方法这些用途,这些情况下,耦合不是什么问题;

(2)有时某些更高级的模式会更符合任务的需要。与“懒汉式”加载单例相比,虚拟代理能给予你对实例化方式更多的控制权。也可以用一个真正的对象工厂来取代分支型单例。

 

本文学习地址:http://www.108js.com/article/article5/50021.html?id=333

												

JavaScript中的单例模式的更多相关文章

  1. JavaScript设计模式,单例模式!

    单例设计模式:保证一个类仅有一个实例,并且提供一个访问它的全局访问点.有些对象只需要一个,这时可用单例模式. 传统的单例模式 和new 创建对象的调用不一样 调用者要调用xxx.getInstance ...

  2. 再起航,我的学习笔记之JavaScript设计模式10(单例模式)

    单例模式 单例模式(Singleton) : 又被称为单体模式,是只允许实例化一次的对象类.一个类有且仅有一个实例,并且自行实例化向整个系统提供. 命名空间 单例模式可能是JavaScript中我们最 ...

  3. 「设计模式」JavaScript - 设计模式之单例模式与场景实践

    单例介绍 上次总结了设计模式中的module模式,可能没有真真正正的使用在场景中,发现效果并不好,想要使用起来却不那么得心应手, 所以这次我打算换一种方式~~从简单的场景中来看单例模式, 因为Java ...

  4. JavaScript 中常见设计模式整理

    开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式.本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知. JavaScript 中常见设计模 ...

  5. JavaScript中常见的十五种设计模式

    在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”. 在JavaScript中并没有类这种概念,JS中的函数属于一等对象,在JS中定义一个对象非常简单(var obj = {}), ...

  6. JavaScript设计模式(单例模式)

    单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个.下面我们来逐步了解单例模式的用法. 一.简版单例模式: var Singleton = funct ...

  7. JavaScript设计模式之单例模式【惰性单例】

    在提高开发水平,往中高级前端工程师中,利用设计模式是必不可少的一条道路.掌握设计模式的思想远远比硬套重要,因为设计模式是一种思想,不局限于开发语言.但实际上由于语言的特性不同,往往在实现的时候会有不少 ...

  8. javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈

    Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...

  9. javascript中的this与函数讲解

    前言 javascript中没有块级作用域(es6以前),javascript中作用域分为函数作用域和全局作用域.并且,大家可以认为全局作用域其实就是Window函数的函数作用域,我们编写的js代码, ...

随机推荐

  1. T-SQL建索引

    USE database GO   ------------开始----------- ALTER TABLE [name] DROP CONSTRAINT 主键约束    ----删除主键约束 IF ...

  2. 第17课-数据库开发及ado.net 聚合函数,模糊查询like,通配符.空值处理.order by排序.分组group by-having.类型转换-cast,Convert.union all; Select 列 into 新表;字符串函数;日期函数

    第17课-数据库开发及ado.net 聚合函数,模糊查询like,通配符.空值处理.order by排序.分组group by-having.类型转换-cast,Convert.union all;  ...

  3. [日常] Go语言圣经--包和文件-包初始化习题

    1.解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化 2.包中含有多个.go源文件,它们将按照发给编译器的顺序进行初始化 3.init初始化函数,在每个文件中的init初始化函数,在程 ...

  4. 一:MyBatis知识整理(1)

    一:MyBatis的架构 1.mybatis配置SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息. mapper.xml文件即sql映射文 ...

  5. 自己写一个java的mvc框架吧(二)

    自己写一个mvc框架吧(二) 自己写代码的习惯 写一个框架吧,如果这个框架会用到一些配置上的东西,我自己习惯是先不用考虑这个配置文件应该是怎样的,什么形式的,先用一个java对象(比如叫 Config ...

  6. 5月9日——vue渲染过程中{{xxx}}显示

    异常显示的原因: 这是由于浏览器的渲染机制导致的,浏览器是从头到尾  如果你的js引用在底部,那么浏览器会先加载dom此时,你用于渲染的{{}}识别符,因为还没读到该识别符对应的js文件,所以会被解析 ...

  7. python 递归和二分法

    一 内置函数 1. revserd 翻转,返回的是迭代器 # 将 s 倒置 s = '不是上海自来水来自海上' # 方法一 print(s[::-1]) # 方法二 s1 = reversed(s) ...

  8. Docker在windows7上的安装

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙箱机制,相互之间不会有任何 ...

  9. 解决Python 爬取ssh证书 的报错问题

    Python3 中会要求添加信任证书,但只是进行爬取数据就没必要了,我们可以忽略它 r1 =requests.get("https://www.baidu.com", verify ...

  10. Linux 修改linux的SSH的默认端口

    修改linux的SSH的默认端口 by:授客 QQ:1033553122 安装完linux后,默认的情况下ssh是开放的,容易造到黑客攻击,简单有效的操作之一就是修改默认端口号   步骤一:修改/et ...