单例继承错误行为

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 单例实例被使用和改变。