【 设计模式就是在某种场合下对特定问题的简洁而又优雅的解决方案 】
【 单例模式是各大模式中较为简单的,也是较为常用且很有用的模式。 在JS中尤为突出(每个对象字面量都可以看做是一个单例~) 】
Singleton 单例模式(单态模式)
1.定义 & 实现思路
确保(一个类)仅有一个实例,并提供全局访问。
单例就是保证一个类只有一个实例.
实现思路:
1.先判断实例存在与否,存在直接返回,如果不存在就创建了再返回.
2.确保一个类只有一个实例对象。
单例模式的思路是:
一个类能返回一个对象的引用(并且永远是同一个)和一个获得该实例的方法(静态方法,通常使用 getInstance 名称)。
当我们调用这个方法时,如果类持有的引用不为空就返回该引用,否者就创建该类的实例,并且将实例引用赋值给该类保持的那个引用再返回。
同时将该类的构造函数定义为私有方法,避免其他函数使用该构造函数来实例化对象,只通过该类的静态方法来得到该类的唯一实例
2.JavaScript 中单例模式的实现
-
在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
-
在 js 开发中,我们经常会把全局变量当成单例来使用
2.1 最简单的对象字面量
再看单例模式的定义:【确保(一个类)仅有一个实例,并提供全局访问】
var Sinleton = {
attr: 1,
show() {
return this.attr
}
}
var t1 = Sinleton
var t2 = Sinleton
console.log(t1 === t2) // true
这样创建的对象 Sinleton ,
- 对象 Sinleton 确实是独一无二的。
- 如果 Sinleton 变量被声明在全局作用域下,那么我们可以在代码中的任何位置使用这个变量。
这样就满足了单例模式的两个条件。
优点:简单且实用
缺点:
- 没有什么封装性,所有的属性方法都是暴露的。对于一些需要使用私有变量的情况就显得心有余而力不足了
- 对于 this 的问题也有一定弊端。
2.2 构造函数内部判断
function Singleton(name, age) {
if(Singleton.unique) {
return Singleton.unique
}
this.name = name
this.age = age
Singleton.unique = this
}
var t1 = new Singleton('cherish', 18)
var t2 = new Singleton('silence', 18)
console.log(t1 === t2) // true
缺点:提出一个属性来做判断,但是也没有安全性,一旦外部修改了Construct的unique属性,那么单例模式也就被破坏了。
2.3 使用闭包实现单例模式
- 使用闭包将创建了的单例缓存起来
var Singleton = (function() {
function Construt() {
}
return new Construt()
})()
var t1 = Singleton
var t2 = Singleton
console.log(t1 === t2) // true
与对象字面量方式类似。不过相对而言更安全一点,当然也不是绝对安全。 如果希望会用调用 single() 方式来使用,那么也只需要将内部的 return 改为
var Singleton = (function() {
var constance = null
return function Construt() {
if(!constance) {
constance = this
}
return constance
}
})()
var t1 = new Singleton('cherish', 18)
var t2 = new Singleton('silence', 18)
console.log(t1 === t2) // true
3.传统方法实现单例模式
3.1 实现一个简单的单例模式(不透明的)
思路:用一个变量来标志 当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象
class Singleton {
constructor(name) {
this.name = name;
this.instance = null;
}
static getInstance(name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
getName() {
return this.name;
}
}
// getInstance:闭包 + 私有变量 的实现
Singleton.getInstance = (function () {
let instance = null;
return function (name) {
if (!instance) {
instance = new Singleton(name);
}
return instance;
};
})();
// 缺点:
// 实现方式不透明。
// 使用者必须通过需要研究代码的实现,才能知道要通过 Singleton.getInstance() 来获取对象。这与我们常见的用 new 关键字来获取对象有出入。
3.2实现透明的的单例模式
var Singleton = (function () {
var instance = null;
var createDiv = function (html) {
if (!instance) {
this.init(html);
}
return (instance = this);
};
createDiv.prototype.init = function (html) {
var divObj = document.createElement("div");
divObj.innerHTML = html;
console.log(document.body.appendChild, divObj, html);
document.body.appendChild(divObj);
};
return createDiv;
})();
var a = new Singleton("<span>aaa</span>");
3.3 缓存代理实现可复用的单例模式
功能拆解: 把 CreateDiv 独立出来, 而负责管理单例的逻辑移到代理类中,保证只有一个对象。 这样一来,CreateDiv 就变成了一个普通的类,它跟 代理 组合起来可以达到单例模式的效果。
class Creatediv {
constructor(html) {
this.init(html);
}
init(html) {
const divObj = document.createElement("div");
divObj.innerHTML = html;
document.body.appendChild(divObj);
}
}
const SingletonProxy = (function () {
let instance = null;
return function (html) {
if (!instance) {
instance = new Creatediv(html);
}
return instance;
};
})();
const a = new SingletonProxy("<span>我是一个span标签1</span>");
const b = new SingletonProxy("<span>我是一个span标签2</span>");
console.log(a === b);
4.前端应用场景
-
浏览器的 window 对象。在 JavaScript 开发中,对于这种只需要一个的对象,往往使用单例实现。
-
遮罩层、登陆浮窗等。
5.其他应用场景
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
如:
-
Windows 的 Task Manager(任务管理器)、Recycle Bin(回收站)。
-
网站的计数器,一般采用单例模式实现,否则难以同步。(计数器会告诉你关于你的网站的某个特定页面上的访问次数)。
-
线程池。多线程的线程池的设计一般采用单例模式,这是由于线程池要方便对池中的线程进行控制。
-
全局缓存等。
6.避免/降低全局变量的命名污染
全局变量容易造成命名空间污染。在大中型项目中,如果不加以限制和管理,程序中可能存在很多这样的变量。JavaScript 中的变量也很容易被不小心覆盖。
作为普通的开发者,我们有必要尽量减少全局变量的使用,即使需要,也要把它的污染降到最低。以下几种方式可以相对降低全局变量带来的命名污染。
开发中我们避免全局变量污染的通常做法如下:
- 全局命名空间
- 使用闭包
它们的共同点是都可以定义自己的成员、存储数据。 区别是全局命名空间的所有方法和属性都是公共的,而闭包可以实现方法和属性的私有化。
6.1使用命名空间
6.1.1对象字面量
var nameSpace = {
a: function () {
console.log("a");
},
b: function () {
console.log("b");
},
};
6.1.2.动态地创建命名空间
var myApp = {};
myApp.nameSpace = function (name) {
var parts = name.split(".");
let current = myApp;
for (let i in parts) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
};
myApp.nameSpace("cherish.name");
myApp.nameSpace("silence.age");
// 上述代码等价于:
var MyApp = {
cherish: {
name: {},
},
silence: {
age: {},
},
};
6.2 使用闭包封装私有变量
var nameSpace = (function () {
let _name = 'cherish'
return {
getInfo() {
return _name
}
}
})()
console.log(nameSpace.getInfo());
7.总结
【 设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象 】
【 在开发中经常会使用中间类,通过它来实现原类所不具有的特殊功能。———— 结合缓存代理,实现可复用的单例模式 】
【 如果严格的只需要一个实例对象的类(虽然JS没有类的概念),那么就要考虑使用单例模式 】
【 使用数据缓存来存储该单例,用作判断单例是否已经生成,是单例模式主要的实现思路 】
参考
JavaScript 设计模式之单例模式
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!