JSのコンストラクタは別のクラスのオブジェクトも返せる

お前は何を言ってるんだ?という感じの記事タイトルだが、JavaScriptのnew演算子はそのクラスと全く関係ないオブジェクトを返すことができるらしい

developer.mozilla.org

If the constructor function returns a non-primitive, this return value becomes the result of the whole new expression. Otherwise, if the constructor function doesn't return anything or returns a primitive, newInstance is returned instead. (Normally constructors don't return a value, but they can choose to do so to override the normal object creation process.)

テキトーに翻訳、

コンストラクタが非プリミティブ値を返した場合、この戻り値がnew式全体の結果となる。もしコンストラクタ関数が値を返さなかったりプリミティブを返した場合は、代わりに newInstance が返される。(普通はコンストラクターは値を返さないが、値を返すことで通常のオブジェクト生成プロセスをオーバーライドすることもできる)

しれっととんでもないことが書いてある。要するに「コンストラクタは暗黙的にthisを返すけど、非プリミティブな値を明示的に返すこともできまっせ」ってことか。えー、つまりこういうこと?

class Dog {}

class Cat {
  constructor() {
    return new Dog()
  }
}

console.log(new Cat())  // -> Dog { } と出力

new CatでDogが返されるというクソコードが爆誕した🙀🙀🙀🙀

「通常のオブジェクト生成プロセスをオーバーライド」ってどういうこと?

コンストラクタに自身のインスタンスが渡されたらスルーしてそのまま返すとか・・・

class Cat {
  constructor(arg) {
    // Catオブジェクトが渡されたらそのまま返す
    if (arg instanceof Cat) {
      return arg
    }
    // それ以外なら通常のオブジェクト生成を実行
    this.name = arg
  }
}

const cat1 = new Cat("ニャン")
const cat2 = new Cat(cat1)

console.log(cat1)            // Cat { name: 'ニャン' }
console.log(cat2)            // Cat { name: 'ニャン' }
console.log(cat1 === cat2);  // true

シングルトンを返すとか・・・

class Cat {
  static singleton = new Cat();

  constructor() {
    return Cat.singleton;
  }
}

const cat1 = new Cat()
const cat2 = new Cat()
const cat3 = new Cat()

console.log(cat1);           // Cat { } 
console.log(cat1 === cat2);  // true
console.log(cat1 === cat3);  // true

これ、なぜsingletonを初期化できるのか不思議なコードなのですが、コンストラクタの仕様によるトリックで意図通り動作します。

static cat = new Cat(); の部分で初めてコンストラクタが呼ばれたとき、static変数singletonはまだ初期化されていないのでundefindがreturnされますが、「コンストラクタがプリミティブをreturnした場合、その戻り値を無視して新しく生成したオブジェクトを返す」という仕様のためCatオブジェクトが返されてsingletonが初期化されます。そして2回目以降のnewでは初期化済みのsingletonが返されるというトリックになっています