[小ネタ]JavaScriptで構造体のようなものを構造体っぽく呼び出す 


# 前説
JavaScriptには俗に「構造体」と呼ばれるものはありません。(ないよね？)
ので、大体はクラスを構造体代わりに

~~~js
function Position(x, y) {
	this.x = x;
	this.y = y;
}

console.log(new Position(0, 1)); // Position {x: 0, y: 1}
~~~

とするわけですが、言語によっては`new`がなくても呼び出せたりします。

なんとなく、構造体であることをはっきりさせるためにこの「newがなくても呼び出せる」をやりたいという。

ちなみに何も考えずにnewを外すと

~~~js
function Position(x, y) {
	this.x = x;
	this.y = y;
}

console.log(new Position(0, 1)); // Position {x: 0, y: 1}
console.log(Position(0, 1)); // undefined
~~~

まあ単なる関数呼び出しなので、returnしていなければこうなります。

# 作ってみよう

## ①コンストラクタ関数で場合分けする

~~~js
function Position(x, y) {
	if (!(this instanceof Position)) {
		return new Position(x, y);
	} // (A)

	this.x = x;
	this.y = y;
}

console.log(new Position(0, 1)); // Position {x: 0, y: 1}
console.log(Position(0, 1)); // Position {x: 0, y: 1}
~~~

コンストラクタを関数として呼び出す場合、関数内部のthisは当然クラスインスタンスにならないので
それを判定に使う感じ。
よく**コンストラクタが関数として呼ばれてしまった場合の補正措置**として登場しますね。

ただこれだと、構造体的なものを定義するたびに
(A)の部分を書かないといけないのがちょっと面倒。

## ②もうちょっと手を加える

構造体コンストラクタを生成する関数を考えてみる。

必要な要件としては

- コンストラクタ呼び出しでも関数呼び出しでも、そのクラスのインスタンスを返す
- 構造によって引数の数や名前が違うのをどうにかする
- 構造体の中身の定義はなるべくシンプルに

あたりでしょうか。

こうなるとファクトリメソッド的なものを作るのが一番ですね。

~~~js
function struct(func) {
	return function() {
		var f = new func();
		func.apply(f, arguments); // (C)
		return f;
	}; // (B)
}

var Position = struct(function Position(x, y) {
	this.x = x;
	this.y = y;
});

var Rect = struct(function Rect(x, y, width, height) {
	this.x = x;
	this.y = y;
	this.width = width;
	this.height = height;
});

console.log(new Position(0, 1)); // Position {x: 0, y: 1}
console.log(Position(0, 1)); // Position {x: 0, y: 1}

console.log(new Rect(0, 1, 2, 3)); // Rect {x: 0, y: 1, width: 2, height: 3}
console.log(Rect(0, 1, 2, 3)); // Rect {x: 0, y: 1, width: 2, height: 3}
~~~

本体となるコンストラクタ関数をstruct関数に送ることで、
(B)でファクトリメソッド的なものを返します。

で、そのファクトリメソッドですが、
コンストラクタごとに引数が異なるので、ほぼFunction#applyでの実装が必須です。
(arguments.lengthを見て場合分けだと限界が…)

ただ、Function#applyは**関数呼び出しであり、コンストラクタ呼び出しではない**ので
applyの結果を返すわけにはいきません。
なので、(C)のようなワンクッション置くような手法を取っています。

## これで完成(？)

…したように見えて、実は問題があります。

~~~js
console.log(new Position(0, 1) instanceof Position); // false
console.log(Position(0, 1) instanceof Position); // false

console.log(new Rect(0, 1, 2, 3) instanceof Rect); // false
console.log(Rect(0, 1, 2, 3) instanceof Rect); // false
~~~

PositionオブジェクトなのにPositionクラスのインスタンスではないという結果。

まあよくよく考えてみると至極当然の話で、Position変数に格納された関数を実行すると
**structの引数に送った関数をクラスとしたときのクラスインスタンス**を返しますが
Position関数自体はただのファクトリメソッドなので、クラスとしての同一性・親子関係はありません。

Positionオブジェクトが返っているのは、structの引数に送った関数の名前が`Position`だからなだけで

~~~js
var Position = struct(function A(x, y) {
	this.x = x;
	this.y = y;
});

console.log(new Position(0, 1)); // A {x: 0, y: 1}
console.log(Position(0, 1)); // A {x: 0, y: 1}
~~~

名前を変えるとこのとおりダメダメでした。

~~~js
function Position(x, y) {
	this.x = x;
	this.y = y;
}

var StructPosition = struct(Position);

console.log(new StructPosition(0, 1) instanceof Position); // true
console.log(StructPosition(0, 1) instanceof Position); // true
~~~

とすることでクラス比較は通りますが…
うーん、そもそも構造体インスタンスと構造体クラスを比較してないのがいまいち。

## ③さらに手を加える

クラス比較が通らないと困るので、

- 返ってくるインスタンスは、ファクトリメソッドのクラスインスタンスである

という要件が追加されます。

これは、②structの引数に送った関数をnewしていたところを
ファクトリメソッド自身をnewすれば解決しそうです。

~~~js
function struct(func) {
	return function() {
		if (!(this instanceof arguments.callee)) {
			var f = new arguments.callee();
			func.apply(f, arguments);
			return f;
		}
		return func.apply(this, arguments);
	};
}

var Position = struct(function(x, y) {
	this.x = x;
	this.y = y;
});

var Rect = struct(function(x, y, width, height) {
	this.x = x;
	this.y = y;
	this.width = width;
	this.height = height;
});

console.log(new Position(0, 1)); // Object {x: 0, y: 1}
console.log(Position(0, 1)); // Object {x: 0, y: 1}

console.log(new Rect(0, 1, 2, 3)); // Object {x: 0, y: 1, width: 2, height: 3}
console.log(Rect(0, 1, 2, 3)); // Object {x: 0, y: 1, width: 2, height: 3}

console.log(new Position(0, 1) instanceof Position); // true
console.log(Position(0, 1) instanceof Position); // true

console.log(new Rect(0, 1, 2, 3) instanceof Rect); // true
console.log(Rect(0, 1, 2, 3) instanceof Rect); // true
~~~

やったね！

気になる点としては

1. `console.log`で表示されるオブジェクトの型が`Object`である
2. 結局のところ、構造体クラスの本体と比較をしていない

あたりになりますが、重要なのは**あるオブジェクトが期待するクラスインスタンスになっているか**
だと思っているので、あるファクトリメソッドが返すオブジェクトが、
必ず単一クラスのインスタンスでさえあればいいのかなーという感じでいます。


## 余談
コンストラクタ呼び出しされた関数の特性として、
**そのクラスインスタンスを返すとは限らない**というものがあります。

~~~js
function A() {
	return 1;
}

function B() {
	return {};
}

function C() {
	return null;
}

function D() {
	return [];
}

function E() {
	return new A();
}

function F() {
	return function G() {};
}

console.log(new A()); // A {}
console.log(new B()); // Object {}
console.log(new C()); // C {}
console.log(new D()); // []
console.log(new E()); // A {}
console.log(new F()); // function G() {}
~~~

詳しくはどこかに仕様が載っていそうな気がしますが
これを見る限り、関数をコンストラクタ呼び出しした時の戻りは

- 必ず参照型である
- returnが明示されていない、または値型をreturnした場合は、その関数をクラスとしたときのクラスインスタンス
- 参照型をreturnした場合はその参照

となっているみたいですね。
今回はこの特性を利用した形になっています。

# 終わりに

構造体を1つ作るのにこんなに関数呼ぶくらいなら
潔く`new`付きで呼び出した方がいいと思いました。