共计 4339 个字符,预计需要花费 11 分钟才能阅读完成。
引入
看完这章之后手写了一个谬误的例子:
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 来取得。
单例模式的简略示例
基于下面的例子,批改 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');
},
}
})();