简单的单例
首先 js 中的函数是一种特殊的对象,这使得他可以存储属性
function aaa(params) {}
//undefined
aaa.lalala = 123
//123
js中的类是通过原型链继承的,因此类就是函数的集合体,可以通过babel编译看看。
因此可以写出简单的单例模式
class Singleton {
constructor(data) {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
this.data = data;
}
getData() {
return this.data;
}
}
const Singleton = require('./Singleton');
const obj1 = new Singleton('Data for the first call');
const obj2 = new Singleton('Data for the second call');
console.log(obj1.getData()); // "Data for the first call"
console.log(obj2.getData()); // "Data for the first call"
可以看到,我们其实是将创建好的实例挂载到了Singleton这个函数对象上,以此维持它的引用。
这样的单例模式十分简单,但若是要继承一下,以便复用呢?
继承的单例
class Singleton {
constructor(data) {
if (this.constructor.instance) {
return this.constructor.instance;
}
this.constructor.instance = this;
this.data = data;
}
getData() {
return this.data;
}
}
class ChildSingleton extends Singleton {
constructor(data) {
super(data);
}
}
new ChildSingleton() //会发生什么?
继承时,情况发生了变化,我们回顾new关键字的工作原理,分析它的过程。
首先,new创建一个新对象,执行原型连接,并将执行函数的this绑定为该对象,就像下面这样
let obj = Object.create(Singleton.prototype)
obj.prototype.constructor = ChildSingleton //其实这里还得设置enumerable: false, writable: true
ChildSingleton.constructor.call(obj,data)
ChildSingleton.constructor()中只有一句 super()
,所以将constructor()压入函数调用栈,执行父类的构造方法,即super()。
constructor(data) {
if (this.constructor.instance) {
return this.constructor.instance;
}
this.constructor.instance = this;
this.data = data;
}
在这里,this指向的依然是new关键字创建的新对象,该对象的原型对象上保存着对函数对象ChildSingleton 的引用,因此 this.constructor.instance
实际上是ChildSingleton.instance
,所以我们能将对象存放在子类函数对象的内部,而不是放在父类中,否则继承时不就乱了。
接下来便是平平无奇的赋值。
然后constructor()从函数调用栈弹出,恢复到子类的构造方法。第一次执行就完成了。
那第二次呢?
前面的步骤都差不多,但是第二次进入时 this.constructor.instance 为true
这导致了return this.constructor.instance; 的执行。
由于return了 父类构造方法直接结束,回到子类构造方法中,由于也没有语句执行了,直接结束。此时,由于父类的return 已经返回了一个对象,所以整个new调用的返回对象就是我们的super() return回来的对象,而不是new创建的那个对象,这里需要注意。
还是不对
如果给子类也添加属性呢?
class Singleton {
constructor(data) {
if (this.constructor.instance) {
return this.constructor.instance;
}
this.constructor.instance = this;
this.data = data;
}
getData() {
return this.data;
}
}
class ChildSingleton extends Singleton {
constructor(data, extraData) {
super(data);
// 添加子类特有的属性
this.extraData = extraData;
}
// 你也可以添加子类特有的方法
getExtraData() {
return this.extraData;
}
}
new ChildSingleton(123,321) === new ChildSingleton() //仔细想想第二次?
这样对吗?对也不对。
虽然控制台打印了true,证明是同一个对象,但是如果打印两次的对象,就会发现extraData属性被改变了!
我直说了:super内的return语句会改变this的指向。
在 return this.constructor.instance; 一句返回之后,this就从new关键字自动创建的新对象被替换成了this.constructor.instance指向的,挂载在ChildSingleton的原型对象上的单例对象。而后代码仍在继续执行,this.extraData = extraData;
因此发现extraData属性的值发生了变化。
解决方式就是防止他在初始化后被构造函数改变。
class ChildSingleton extends Singleton {
constructor(data, extraData) {
super(data);
// 如果 extraData 属性不存在,那么我们才设置它
if (!this.extraData) {
this.extraData = extraData;
}
}
// 你也可以添加子类特有的方法
getExtraData() {
return this.extraData;
}
}
如此便好。
标签:Singleton,return,extraData,JS,instance,单例,constructor,data From: https://www.cnblogs.com/lacia/p/18384238/js-singular-mode-super-return-changes-this-1n8ugm