通常の {...}
構文では、1つのオブジェクトを作成できます。しかし、複数のユーザやメニューアイテムなど、似たようなオブジェクトを多数作成する必要がある場合もあります。
このようなことは、コンストラクタ関数と "new"
演算子を使うことで実現できます。
コンストラクタ 関数
コンストラクタ関数は技術的には通常の関数です。それには2つの慣習があります:
- 名前は大文字で始めます。
"new"
演算子を使ってのみ実行されるべきです。
例:
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
new
演算子を用いて関数が実行された場合、次のような処理が行われます:
- 新しい空のオブジェクトが作成され、
this
に割り当てられます。 - 関数本体を実行します。通常は新しいプロパティを追加することで
this
に変更を加えます。 this
の値が返されます。
つまり、new User(...)
は次のようなことを行います:
function User(name) {
// this = {}; (暗黙)
// this へプロパティを追加
this.name = name;
this.isAdmin = false;
// return this; (暗黙)
}
したがって、let user = new User("Jack")
は、以下と同じ結果となります:
let user = {
name: "Jack",
isAdmin: false
};
もし他のユーザを作りたいのであれば、new User("Ann")
、new User("Alice")
と言ったように呼び出すことができます。毎回リテラルを使うよりはるかに短く、また読みやすくなります。
再利用可能なオブジェクト作成のコードを実装すること、それがコンストラクタの主な目的です。
改めて留意しておきましょう。技術的にはどのような関数(this
を持たないアロー関数を除く)でもコンストラクタとして使用できます。つまり、どの関数も new
で実行することができ、上記のアルゴリズムが実行されることになります。“先頭が大文字” というのは、関数が new
で実行されることを明確にするための共通の合意です。
1つの複雑なオブジェクトを作成するためのコードがたくさんある場合、次のように即座に呼び出されるコンストラクタ関数でラップすることができます:
let user = new function() {
this.name = "John";
this.isAdmin = false;
// ...ユーザ作成のための他のコード。
// 複雑なロジック、文
// ローカル変数などを持つかもしれません。
};
このコンストラクタはどこにも保存されておらず、単に作って呼び出されただけなので再び呼び出すことはできません。したがってこのトリックは、将来の再利用は考えず、単一のオブジェクトを構成するコードをカプセル化することを目的としています。
コンストラクタの呼び出しモードの確認: new.target
このセクションで登場する構文はめったに使われないため、すべてについて知りたいのでなれけばスキップしても構いません。
関数の中では、new.target
という特別なプロパティを使うことで、new
を使って呼び出されたのかそうでないのかを確認することができます。
このプロパティは、通常の呼び出しでは undefined
であり、 new
で呼び出された場合はその関数と等しくなります:
function User() {
alert(new.target);
}
// new なし:
User(); // undefined
// new あり:
new User(); // function User { ... }
これを関数内で使用することにより、new
を付けて「コンストラクタモード」で呼び出されたのか、あるいは new
を付けずに「通常モード」で呼び出されたのかを知ることができます。
new
の場合と、通常呼び出し両方の構文が同じように動作するようにするために使用することもできます:
function User(name) {
if (!new.target) { // new なしで実行した場合
return new User(name); // ...new を追加します
}
this.name = name;
}
let john = User("John"); // new User へのリダイレクト
alert(john.name); // John
このアプローチは、構文をより柔軟にするためにライブラリ中で使われることがあります。new
の有無に関わらず同じ動作をするような、関数の呼び出しを可能とするためです。
new
を省略すると何が起こっているのかが少しわかりにくくなるため、どこへでもこれを使うのは良いことではないでしょう。new
があれば、新しいオブジェクトが作られることを知ることができます。
コンストラクタからの返却
通常、コンストラクタは return
文を持ちません。コンストラクタの仕事は、必要なものをすべて this
の中に書き込むことであり、this
が自動的に戻り値となります。
しかし、return
文がある場合のルールはシンプルです:
- もし
return
がオブジェクトと一緒に呼ばれた場合、this
の代わりにオブジェクトが返されます。 - もし
return
がプリミティブと一緒に呼ばれた場合、それは無視されます。
言い換えると、オブエジェクトのreturn
はそのオブジェクトを返し、それ以外のケースでは this
が返されます。
例えば、ここで return
はオブジェクトと共に呼ばれているため、this
より優先してこのオブジェクトが返されます:
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- オブジェクトを返す
}
alert( new BigUser().name ); // Godzilla, オブジェクトを取得 ^^
そして、これは空の return
の例です(return
の後にプリミティブを置いた場合も同じです)
function SmallUser() {
this.name = "John";
return; // <-- returns this
}
alert( new SmallUser().name ); // John
通常、コンストラクタは return
文を持ちません。ここで、オブジェクトを返すときの特別な動作について言及しているのは、主に網羅性のためです。
ところで、もし引数を持たない場合は、new
の後の丸括弧を省略することもできます。
let user = new User; // <-- 括弧なし
// これと同じ
let user = new User();
丸括弧の省略は “良いスタイル” ではありませんが、この構文は仕様で認められています。
コンストラクタの中のメソッド
オブジェクトを作るとき、コンストラクタ関数を使用することで高い柔軟性を得ることができます。コンストラクタ関数はオブジェクトがどのように組み立てられるか、その中に何を置くかを定義するパラメータを持っています。
もちろん、this
にはプロパティだけではなく、同様にメソッドも追加することができます。
例えば、下の new User(name)
は、name
と sayHi
メソッドを持つオブジェクトを作ります:
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
複雑なオブジェクトを作るために、より高度な構文 classes があります。これに関しては後ほど説明します。
サマリ
- コンストラクタ関数(もしくは簡潔にコンストラクタ)は通常の関数ですが、大文字から始まる名前を持つと言う共通の合意があります。
- コンストラクタ関数は
new
を使ってのみ呼び出されるべきです。この呼び出しは、最初に空のthis
を作成し、必要な処理が行われたthis
を最後に返すことを意味します。
複数の似たようなオブジェクトを作るために、コンストラクタ関数を使うことができます。
JavaScript には、日付を表す Date
や集合を表す Set
、そしてこのあと私たちが学ぶ予定のものなど、多くの組み込みオブジェクトに対するコンストラクタ関数が用意されています。
このチャプターでは、オブジェクトとコンストラクタについての基礎のみを説明しています。これらは、次の章でデータ型と関数についてより深く学ぶために不可欠です。
それらを学んだあとオブジェクトに戻り、プロトタイプ, 継承 と クラス の各章で詳しく説明します。