《JavaScript设计模式与开发实践》阅读笔记之单例模式
引入
看完这章之后手写了一个错误的例子:
let SingleTip = function(content) {
this.content = content;
this.showContent = function() {
console.log(this.content);
};
this.instance = null;
this.getInstance = function(content) {
if (this.instance) {
return this.instance;
}
this.instance = new SingleTip(content);
return this.instance;
};
}// 报错:SingleTip.getInstance is not a function
// let tip1 = SingleTip.getInstance('plz sit down');
// let tip2 = SingleTip.getInstance('plz stand up');
let singleTip = new SingleTip();
let tip1 = singleTip.getInstance('plz sit down');
let tip2 = singleTip.getInstance('plz stand up');
console.log(tip1 === tip2);
// 测试是否为同一个实例
这里的本意是假想某个网页内只允许存在一个tip。当我使用SingleTip.getInstance函数时,报错其并非函数。错就错在getInstance这样的定义方式不对,只能实例化之后才能使用这个函数,这不扯呢?
单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。所以正确姿势应该是通过类名SingleTip来获得单例,而不是通过对象名singleTip来获得。
单例模式的简单示例 【《JavaScript设计模式与开发实践》阅读笔记之单例模式】基于上面的例子,修改getInstance的定义方式:
let SingleTip = function(content) {
this.content = content;
this.showContent = function() {
console.log(this.content);
};
this.instance = null;
// 原书示例,此处似乎无必要
}SingleTip.getInstance = function(content) {
if (this.instance) {
return this.instance;
}
this.instance = new SingleTip(content);
return this.instance;
}let tip1 = SingleTip.getInstance('plz sit down');
let tip2 = SingleTip.getInstance('plz stand up');
console.log(tip1 === tip2);
// true
这里示例代码未实现content修改。不管调用SingleTip.getInstance时传参如何,获取的实例都是首次调用时获取的实例,且content一直为'plz sit down'。
注意到SingleTip.getInstance的函数体,里面的this指向的是SingleTip(构造函数,本身也是一个对象),最终SingleTip的属性就包括getInstance和instance两个。相比之下,“this.instance = null; ”这个instance属性会放到tip1(对象实例),反而并无必要。
我们还可以把SingleTip.getInstance修改为立即执行函数的形式,并且令其返回结果是一个函数,把instance放在函数闭包中:(有啥好处没看出来)
let SingleTip = function(content) {
this.content = content;
this.showContent = function() {
console.log(this.content);
};
}SingleTip.getInstance = (function(content){
let instance;
return function(content) {
if (instance) {
return instance;
}
instance = new SingleTip(content);
return instance;
}
})();
let tip1 = SingleTip.getInstance('plz sit down');
let tip2 = SingleTip.getInstance('plz stand up');
console.log(tip1 === tip2);
更“透明”的单例实现 上面的两个示例代码,存在的问题是依赖getInstance来获取实例,谓之“不够透明”。能不能像普通类一样,通过new关键字来获取实例,而“单例”的限定在类定义里面处理呢?
立即执行函数就相当于一段相对独立的代码空间,里面可以做相对独立的处理,最后返回一个外界需要的东西。这里就是返回了一个_SingleTip,被SingleTip包裹,用SingleTip类名来获取_SingleTip类的一个实例:
let SingleTip = (function(){
let _instance;
let _SingleTip = function(content) {
if (_instance) {
return _instance;
}
this.init(content);
_instance = this;
return _instance;
};
// 报错:this.init is not a function
// 注意_SingleTip.init还没定义,不能提前调用,只能把init放在对应的原型上定义
// _SingleTip.init = function(content) {}
_SingleTip.prototype.init = function(content) {
this.content = content;
this.showContent = function() {
console.log(this.content);
};
}return _SingleTip;
})();
let tip1 = new SingleTip('plz sit down');
let tip2 = new SingleTip('plz stand up');
console.log(tip1 === tip2);
更重要的是,这里形式上用类似普通类一样的new SingleTip()就可以获取单例,SingleTip内部再做“单例”限定,更“透明”了。
代理模式实现单例 从形式上来看,上一个示例中好不容易用立即执行函数将getInstance作用“合”到SingleTip中,用new SingleTip()即可获取单例。现在,我们重新做“拆分”,目的是拆分上面示例中立即执行函数内部的两个步骤:创建对象、管理单例。
let Tip = (function(){
let _Tip = function(content) {
this.init(content);
};
_Tip.prototype.init = function(content) {
this.content = content;
this.showContent = function() {
console.log(this.content);
};
}return _Tip;
})();
let SingleTipProxy = (function(){
let _instance;
return function(content){
if (_instance) {
return _instance;
}
_instance = new Tip(content);
return _instance;
}
})();
let tip1 = new SingleTipProxy('plz sit down');
let tip2 = new SingleTipProxy('plz stand up');
console.log(tip1 === tip2);
如此拆分,创建对象的代码在Tip中,管理单例的代码在SingleTipProxy中。Tip部分可以复用、创建普通对象;如需创建单例,则结合SingleTipProxy使用。
JavaScript中的单例模式 JavaScript中的全局对象,天然就便于当成单例使用。因为:
单例模式的核心是确保只有一个实例,并提供全局访问。我们需要进一步做限制,防止全局对象被覆盖定义、命名冲突,从而确保全局只有一个实例。通常有如下做法:
- 使用命名空间
JavaScript的命名空间不是什么复杂的概念,使用对象字面量即可创建简单的命名空间:
let namespace1 = {
name: 'lucy',
doSomething: function() {
console.log('doSomething');
}
};
let namespace2 = {
name: 'nancy',
doSomething: function() {
console.log('DO SOMETHING');
}
};
书中提到了一种动态创建命名空间的做法:
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 MyAppMyApp = {
event: {},
dom: {
style: {}
}
};
- 使用闭包封装变量
把变量(name)再封装一层,暴露接口(getName,闭包引用、返回变量)供外界获取。
let namespace1 = (function(){
let _name = 'lucy';
return {
getName: function() {
return _name;
},
doSomething: function() {
console.log('do something');
},
}
})();
let namespace2 = (function(){
let _name = 'nancy';
return {
getName: function() {
return _name;
},
doSomething: function() {
console.log('DO SOMETHING');
},
}
})();
推荐阅读
- Android 入门级教程 《第一行代码》 中遇到的问题
- 《基地》读后感
- 2017-2018-2 20165218 实验四《Android开发基础》实验报告
- java实验四《Android程序设计》实验报告
- 2017-2018-2 20165331 实验四《Android开发基础》实验报告
- 2017-2018-2 20165215 实验四《Android开发基础》实验报告
- 20165229 实验四 《Android程序设计》 实验报告
- 2017-2018-2 20165236 实验四《Android开发基础》实验报告
- 2017-2018-2 20165336 实验四《Android开发基础》实验报告
- 2017-2018-2 20165228 实验四《Android程序设计》实验报告