概念单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。UML类图场景单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏 览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少 次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。优缺点优点:创建对象和管理单例的职责被分布在两个不同的方法中实现1. 我们的第一个单例var instance = nullvar getInstance = function(arg) { if (!instance) { instance = arg } return instance}var a = getInstance(‘a’)var b = getInstance(‘b’)console.log(a===b)这种定义一个全局变量的方式非常不优雅,也不好复用代码2. 利用闭包实现单例var Singleton = function( name ){ this.name = name;};Singleton.getInstance = (function(){ var instance = null; return function( name ){ if ( !instance ){ instance = new Singleton( name ); } return instance; }})();var a = Singleton.getInstance(‘a’)var b = Singleton.getInstance(‘b’)console.log(a===b)有些同学可能对闭包不大理解,下面用函数实现一下3. 利用函数实现单例function Singleton(name) { this.name = name this.instance = null}Singleton.getInstance = function(name) { if (!this.instance) { this.instance = new Singleton(name) } return this.instance}var a = Singleton.getInstance(‘a’)var b = Singleton.getInstance(‘b’)console.log(a===b)2,3这两种方式也有缺点,就是我们必须调用getInstance来创建对象,一般我们创建对象都是利用new操作符4. 透明的单例模式var Singleton = (function() { var instance Singleton = function(name) { if (instance) return instance this.name = name return instance = this } return Singleton})()var a = new Singleton(‘a’)var b = new Singleton(‘b’)console.log(a===b)这中方法也有点缺点:不符合单一职责原则,这个对象其实负责了两个功能:单例和创建对象下面我们分离这两个职责5. 利用代理实现单例var People = function(name) { this.name = name}var Singleton = (function() { var instance Singleton = function(name) { if (instance) return instance return instance = new People(name) } return Singleton})()var a = new Singleton(‘a’)var b = new Singleton(‘b’)console.log(a===b)这中方法也有点缺点:代码不能复用。如果我们有另外一个对象也要利用单例模式,那我们不得不写重复的代码6. 提供通用的单例var People = function(name) { this.name = name}var Singleton = function(Obj) { var instance Singleton = function() { if (instance) return instance return instance = new Obj(arguments) } return Singleton}var peopleSingleton = Singleton(People)var a = new peopleSingleton(‘a’)var b = new peopleSingleton(‘b’)console.log(a===b)到这里已经比较完美了,等等这只是es5的写法,下面我们用es6来实现一下7. es6单例模式class People { constructor(name) { if (typeof People.instance === ‘object’) { return People.instance; } People.instance = this; this.name = name return this; }}var a = new People(‘a’)var b = new People(‘b’)console.log(a===b)比较以上几种实现用全局变量的第1种方法,应该摒弃用闭包实现的第2种方式,instance 实例对象总是在我们调用 Singleton.getInstance 的时候才被创建,应该摒弃其他方式都是惰性单例(在需要时才创建)js的特殊性我们都知道:JavaScript 其实是一门无类(class-free)语言,,生搬单例模式的概念并无意义。单例模式的核心是确保只有一个实例,并提供全局访问。我们可以用一下几种方式来另类实现1. 全局变量比如var a = {},这时全局就只有一个a对象但全局变量存在很多问题,它很容易造成命名空间污染,我们用以下两种方式解决2.使用命名空间 var namespace1 = { a: function () { alert(1); }, b: function () { alert(2); } };另外我们还可以动态创建命名空间 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]]; } }; MyApp.namespace(’event’); MyApp.namespace(‘dom.style’); console.dir(MyApp); // 上述代码等价于: var MyApp = { event: {}, dom: { style: {} } };3. 闭包 var user = (function () { var __name = ‘sven’, __age = 29; return { getUserInfo: function () { return __name + ‘-’ + __age; } } })();例子登录框下面我们来实现一个点击登录按钮弹出登录框的例子粗糙的实现<html><body> <button id=“loginBtn”>登录</button></body><script> var loginLayer = (function () { var div = document.createElement(‘div’); div.innerHTML = ‘我是登录浮窗’; div.style.display = ’none’; document.body.appendChild(div); return div; })(); document.getElementById(’loginBtn’).onclick = function () { loginLayer.style.display = ‘block’; };</script></html>上面这种方式如果用户没有点击登录按钮,也会在一开始就创建登录框改进<html><body> <button id=“loginBtn”>登录</button></body><script> var createLoginLayer = function () { var 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’; };</script></html>这种方式每次点击按钮都会创建一个登录框再改进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’; };这种方式不够通用,不符合单一职责原则再再改进 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 createSingleLoginLayer = getSingle(createLoginLayer); document.getElementById(’loginBtn’).onclick = function () { var loginLayer = createSingleLoginLayer(); loginLayer.style.display = ‘block’; }; //下面我们再试试创建唯一的iframe 用于动态加载第三方页面: var createSingleIframe = getSingle(function () { var iframe = document.createElement(‘iframe’); document.body.appendChild(iframe); return iframe; }); document.getElementById(’loginBtn’).onclick = function () { var loginLayer = createSingleIframe(); loginLayer.src = ‘http://baidu.com’; };至此已经完美