单例继承错误行为
Singleton Inheritance Buggy Behavior
我发现 javascript 使用单例模式的 es6 继承中存在错误行为。
代码为:
let instanceOne = null;
class One {
constructor() {
if (instanceOne) return instanceOne;
this.name = 'one';
instanceOne = this;
return instanceOne;
}
method() {
console.log('Method in one');
}
}
let instanceTwo = null;
class Two extends One {
constructor() {
super();
if (instanceTwo) return instanceTwo;
this.name = 'two';
instanceTwo = this;
return instanceTwo;
}
method() {
console.log('Method in two');
}
}
const objOne = new One();
const objTwo = new Two();
console.log(objOne.name);
console.log(objTwo.name);
objOne.method();
objTwo.method();
显示为:
two
two
Method in one
Method in one
继承不知何故搞砸了。此处属性被覆盖,但对象方法未被覆盖。
我的问题是它为什么有效(就像现在抛出),你能解释一下这个行为吗?
看来新对象需要全新的对象作为父对象(参见下面的解决方案)。
如果您遇到同样的问题,这是我的解决方案:
let instanceOne = null;
class One {
constructor(brandNewInstance = false) {
if (instanceOne && !brandNewInstance) return instanceOne;
this.name = 'one';
if (brandNewInstance) return this;
instanceOne = this;
return instanceOne;
}
method() {
console.log('Method in one');
}
}
let instanceTwo = null;
class Two extends One {
constructor() {
super(true);
if (instanceTwo) return instanceTwo;
this.name = 'two';
instanceTwo = this;
return instanceTwo;
}
method() {
console.log('Method in two');
}
}
我用的是node.js v6.9.1
这是因为这一行:
if (instanceOne) return instanceOne;
One
构造函数在上面的代码中运行了两次。第二个 One
调用是 super()
,在本例中 this
是从 Two.prototype
创建的,对象方法是 Two.prototype.method
.
来自 super()
的 Return 语句将 this
替换为 One
单例,然后 Two
构造函数仅修改 One
单例实例。
可以使用静态 属性 来保存实例:
constructor() {
if (this.constructor.hasOwnProperty('instance'))
return this.constructor.instance;
this.constructor.instance = this;
this.name = 'one';
}
或者如果与后代共享实例 类 是预期的行为,
constructor() {
if ('instance' in this.constructor)
return this.constructor.instance;
this.name = 'one';
this.constructor.instance = this;
}
在这种情况下,所有单例机制都由 One
构造函数完成,Two
只需要调用 super
:
constructor() {
super();
this.name = 'two';
}
此外,结束 return
语句是多余的。 this
不必明确返回。
你做的事情有点奇怪。 ecmascript 6 中的构造函数和 subclasses 并不像您认为的那样工作。您可能希望阅读 this blog post(尤其是第 4 部分)以了解更多信息。
根据那篇文章,您的代码在幕后看起来像这样:
let instanceOne = null;
function One() {
// var this = Object.create(new.target.prototype); // under the hood
if (instanceOne) return instanceOne;
this.name = 'one';
instanceOne = this;
return instanceOne;
}
One.prototype.method = function() { console.log('Method in one'); }
let instanceTwo = null;
function Two() {
var that = undefined;
that = Reflect.construct(One, [], new.target);
if (instanceTwo) return instanceTwo;
that.name = 'two';
instanceTwo = that;
return instanceTwo;
}
Two.prototype.method = function() { console.log('Method in two'); }
Object.setPrototypeOf(Two, One);
Object.setPrototypeOf(Two.prototype, One.prototype);
const objOne = Reflect.construct(One, [], One);
const objTwo = Reflect.construct(Two, [], Two);
console.log(objOne.name);
console.log(objTwo.name);
objOne.method();
objTwo.method();
(new.target 是作为 Reflect.construct
的第三个参数传递的值)
您可以看到对于Two
class,没有创建新对象并且没有使用Two.prototype
。相反,One
单例实例被使用和改变。
我发现 javascript 使用单例模式的 es6 继承中存在错误行为。
代码为:
let instanceOne = null;
class One {
constructor() {
if (instanceOne) return instanceOne;
this.name = 'one';
instanceOne = this;
return instanceOne;
}
method() {
console.log('Method in one');
}
}
let instanceTwo = null;
class Two extends One {
constructor() {
super();
if (instanceTwo) return instanceTwo;
this.name = 'two';
instanceTwo = this;
return instanceTwo;
}
method() {
console.log('Method in two');
}
}
const objOne = new One();
const objTwo = new Two();
console.log(objOne.name);
console.log(objTwo.name);
objOne.method();
objTwo.method();
显示为:
two
two
Method in one
Method in one
继承不知何故搞砸了。此处属性被覆盖,但对象方法未被覆盖。
我的问题是它为什么有效(就像现在抛出),你能解释一下这个行为吗?
看来新对象需要全新的对象作为父对象(参见下面的解决方案)。
如果您遇到同样的问题,这是我的解决方案:
let instanceOne = null;
class One {
constructor(brandNewInstance = false) {
if (instanceOne && !brandNewInstance) return instanceOne;
this.name = 'one';
if (brandNewInstance) return this;
instanceOne = this;
return instanceOne;
}
method() {
console.log('Method in one');
}
}
let instanceTwo = null;
class Two extends One {
constructor() {
super(true);
if (instanceTwo) return instanceTwo;
this.name = 'two';
instanceTwo = this;
return instanceTwo;
}
method() {
console.log('Method in two');
}
}
我用的是node.js v6.9.1
这是因为这一行:
if (instanceOne) return instanceOne;
One
构造函数在上面的代码中运行了两次。第二个 One
调用是 super()
,在本例中 this
是从 Two.prototype
创建的,对象方法是 Two.prototype.method
.
super()
的 Return 语句将 this
替换为 One
单例,然后 Two
构造函数仅修改 One
单例实例。
可以使用静态 属性 来保存实例:
constructor() {
if (this.constructor.hasOwnProperty('instance'))
return this.constructor.instance;
this.constructor.instance = this;
this.name = 'one';
}
或者如果与后代共享实例 类 是预期的行为,
constructor() {
if ('instance' in this.constructor)
return this.constructor.instance;
this.name = 'one';
this.constructor.instance = this;
}
在这种情况下,所有单例机制都由 One
构造函数完成,Two
只需要调用 super
:
constructor() {
super();
this.name = 'two';
}
此外,结束 return
语句是多余的。 this
不必明确返回。
你做的事情有点奇怪。 ecmascript 6 中的构造函数和 subclasses 并不像您认为的那样工作。您可能希望阅读 this blog post(尤其是第 4 部分)以了解更多信息。
根据那篇文章,您的代码在幕后看起来像这样:
let instanceOne = null;
function One() {
// var this = Object.create(new.target.prototype); // under the hood
if (instanceOne) return instanceOne;
this.name = 'one';
instanceOne = this;
return instanceOne;
}
One.prototype.method = function() { console.log('Method in one'); }
let instanceTwo = null;
function Two() {
var that = undefined;
that = Reflect.construct(One, [], new.target);
if (instanceTwo) return instanceTwo;
that.name = 'two';
instanceTwo = that;
return instanceTwo;
}
Two.prototype.method = function() { console.log('Method in two'); }
Object.setPrototypeOf(Two, One);
Object.setPrototypeOf(Two.prototype, One.prototype);
const objOne = Reflect.construct(One, [], One);
const objTwo = Reflect.construct(Two, [], Two);
console.log(objOne.name);
console.log(objTwo.name);
objOne.method();
objTwo.method();
(new.target 是作为 Reflect.construct
的第三个参数传递的值)
您可以看到对于Two
class,没有创建新对象并且没有使用Two.prototype
。相反,One
单例实例被使用和改变。