JavaScript

function リテラルでのコンストラクタは Object.create() するとクラス名が消える?

More than 5 years have passed since last update.

JavaScript を書いてる人はおそらく普段このようなコードでクラスとインスタンスを記述するかと思います。

var Hoge = function (foo, bar) {
  this.foo = foo;
  this.bar = bar;
};
var hoge = new Hoge('hello', 'world');

しかし、ES5 で導入された Object.create() でこのオブジェクトを複製すると、Firefox や WebKitのコンソールで Object と出力されてしまいます。

実際には プロトタイプチェーンを引き継いでいますし、以下のような実行結果になります。

var hogeClone = Object.create(hoge);
console.log(hogeClone.constructor === Hoge); // true
console.log(hogeClone instanceof Hoge); // true

これは、var による変数に無名の function リテラルでコンストラクタを宣言している場合に起きる現象です。デバッガがオブジェクトのコンストラクタを認識する時に、hoge.constructor.name を参照するので、無名関数リテラルではその値が空と判断され、「それじゃあ出力で表示するクラス名は タダのObjectでいいね」と判断するためです。

console.log(hoge.constructor.name); // ""

ちゃんとデバッガがクラス名を出力するパターン

var Hoge = function Hoge(foo, bar){
  this.foo = foo;
  this.bar = bar;
};

// Firefox の Firebug だと、これでもダメ。
// function Hoge () {} と完全に function 文にする必要あり。

var hoge = new Hoge('hey!', 'you!');

console.log(hoge.constructor.name); // "Hoge"

このようにリテラルでなく、文としてコンストラクタを宣言すると、デバッガは function.name を参照してインスタンスをクラス名で表示してくれるようになります。

「実行に影響ないよね?」← やり方によってはあるよ

function 文で書いてしまうと、スコープの一番先頭に移動されるので、リテラルでの宣言と挙動が変わるケースは存在します。

しかし、大抵の場合、クラスのコンストラクタ宣言でそうした影響の可能性はほぼ無いかと思われます。(別にコンストラクタがグローバルリークするわけでもないし、宣言した箇所のスコープは保持するので)
特に var Hoge = function Hoge() {} のように名前つき function リテラル で宣言する場合はほとんど影響ないです。

じゃあ、してもしなくてもあんまり変わらないのかというと、デバッグしやすさにおいてかなり影響があります。

すべて var でコンストラクタを定義してあると、せっかくクラスを定義して、メソッドやプロトタイプを当て、役割を決めているのにデバッガですべて Object として出力されてしまってはもったいないですよね。