JavaScriptで即時関数(function(){…})()を使う

JavaScriptには無名関数を定義・宣言すると同時に即時実行するための構文、即時関数というものがある。
この即時関数を利用することで、疑似的にブロックスコープを再現できる。

 

即時関数の構文

基本的には、以下のような構文となる。(他にも記述方法がいくつかある)

また、以下のように引数を指定することもできる。

その他、通常の関数と同様に戻り値を持たせることも可能である。
※この場合、functionを丸括弧で括る必要はない。(理由は後述)

 

functionを丸括弧で括る目的

即時関数はfunctionを丸括弧で括ることで実現されているがこの目的は、「関数を式として評価させる為」である。
JavaScriptにはfunction文とfunction式があり、以下のようにfunctionから始まる関数がfunction文、 式の中で関数を定義したもの(つまりfunction以外で始まるもの)がfunction式となる。

因みにfunction文とfunction式では挙動が異なり、function文の場合は実行された際に関数が常に先頭に定義される。その為、function文はコード上のどの位置に存在しても対象の関数を呼び出すことができる。しかし、function式は読込まれた位置で関数が定義される為、記述位置よりも前で実行することはできない。
また、function式であれば、関数呼び出しの演算子”()”をつけて関数定義と同時に関数呼び出しができる。
つまり、即時関数として関数を定義する上でfunctionを丸括弧で括る目的は、関数をfunction文ではなく、function式として扱う為ということになる。
戻り値を変数に代入する即時関数の場合、functionを丸括弧で括る必要がないのも既にfunction式として評価されている為である。
因みにfunction文に関数呼び出しの演算子”()”をつけるとSyntax Errorとなる。

これはfunction文が”}”で終わったとみなされ、以下のようなイメージで解釈される為である。

“();”は、JavaScriptとして不正な構文となる為、ここでエラーが発生する。
繰り返しになってしまうが、この挙動を回避する為にfunction式の記述にする必要があり、その方法としてfunctionを丸括弧で括る(function以外で始まる)ということに繋がる。

 

即時関数を利用する目的

即時関数を利用する目的は、スコープを汚染せずに新たなスコープを作成することにある。
JavaScriptのスコープは、「グローバルスコープ」と「関数スコープ(ローカルスコープ)」のみであり、関数は任意にスコープを作る手段と成り得る。
関数スコープの中でvarを使って定義された変数は関数の中でローカルな変数になるので、関数の外側の変数を上書きしない。
再利用されることがない一連の処理の中で一時変数を使用する際、それらの変数をすべてグローバル変数とするとグローバルスコープに不要な変数が積み上がっていき、変数名のバッティング等の予期せぬトラブルが発生する可能性がある。
一連の処理を即時関数で包み一時的なスコープの中で実行することで、プログラムをスコープ上のサンドボックス内で実行することになり、スコープの外側への影響を防ぐことができる。
また、即時関数を利用することで関数そのものを格納する変数も必要がなくなる為、グローバルスコープを全く汚染せずに一時変数を関数スコープ内に包み込むことができる。
その他、一連の処理の結果が不変で結果のみが必要な場合、その結果を得るために何度も同じ処理を実行する必要がなくなるメリットもある。

 

即時関数の利用イメージ

<初期化処理への利用>
ページ読み込み時の初期化処理(一度限りの実行)に利用する。

以下の例は、”label_date”ラベルを用意してページ読み込みと同時に日時を表示している。
一時変数である”label”、”now”、そして処理そのものは初期化終了後には不要となるので、即時関数で包みグローバルスコープの汚染を回避できる。

以下の例は、ユーザーエージェントの判定を行っている。
判定結果のみが必要であり、判定処理を即時関数とすることで、その結果を取得する為に何度も判定処理を行う必要がなくなる。

<プライベートプロパティ/メソッドの定義への利用>
JavaScriptには、privateやprotected等のアクセス修飾子が無い為、オブジェクト内に定義されたプロパティ/メソッドは、全てパブリックなものとして扱われる。
しかし、即時関数とクロージャを利用することで、疑似的にプライベートなプロパティ/メソッドを実現することができる。

 

即時関数のいろいろな記述方法

即時関数は、function式として認識させられる構文であれば記述することができる。
つまり、単項演算子から始まる関数を記述すればよい。
単項演算子には”+”、”-“、”!”、”void”、”typeof”等があり、これらも即時関数の記述に利用できる。

上記は、全て動作する。
但し、丸括弧を利用した場合は戻り値に変化がないが、”+”、”-“の場合は戻り値がnumber型にキャストされ、”!”の場合は戻り値がbool型にキャストされ、”void”の場合は戻り値が常に”undefined”となる。
因みに”new”も単項演算子であるが、スコープ内でのthisの示すものが変わってくるので即時関数を記述する目的での使用は控えた方が良いかと思われる。

 

JavaScriptにおけるObject.createの引数

ECMAScript5で定義されたObject.createを利用するとコンストラクタ関数とnew演算子を利用する方法以外でオブジェクトを生成することができる。

Object.createを利用する

上記のようにObject.createに元となるオブジェクトをプロトタイプとして渡すことで、新しいオブジェクトを生成することができる。
但し、そもそもコンストラクタ関数が無いのでコンストラクタ引数に初期値を与えることができない。

 

Object.createの第二引数

Object.createを利用する上でコンストラクタ引数に相当するものは、第二引数になる。

尚、Object.createの第二引数は以下のように形式が厳密に定義されている。

フィールド既定値内容
valueundefinedプロパティの値。データディスクリプタのみ。
writablefalsetrueの場合、値を変更可能。データディスクリプタのみ。
configurablefalsetrueの場合、ディスクリプタ変更や、オブジェクトからプロパティ削除が可能。
enumerablefalsetrueの場合、対応するオブジェクトのプロパティ列挙に現れる。
getundefinedGetter関数。アクセサディスクリプタのみ。
setundefinedSetter関数。アクセサディスクリプタのみ。

上記の規定に従い、オブジェクト生成時に初期値を与えてみる。

上記の例では、プロトタイプに定義したnameの値がオブジェクト生成時に指定した値に書き換わっている。
また、同時に新しいメソッド(bark)を追加している。
尚、nameプロパティを書き換え可能にするには、”{value: “SANTA”, writable: true}”のようにwritableフィールドにtrueを設定する必要がある。
同様にプロパティ列挙に表示したい場合やプロパティの削除を可能にしたい場合も、対応するフィールドの設定をtrueに変更する必要がある。

 

JavaScriptにおける「this」が指し示すもの

JavaScriptにおける「this」は、「呼び出し元」で指し示すものが変わってくる。
基本的に以下の4種類のパターンが存在する。

  • 関数呼び出し(グローバルオブジェクト)のパターン
  • メソッド呼び出し(所属オブジェクト)のパターン
  • コンストラクタ呼び出し(インスタンス自身)のパターン
  • apply/call呼び出し(強制変更)のパターン

 

関数呼び出し(グローバルオブジェクト)のパターン

この場合、「this」は「グローバルオブジェクト」を指す。
つまり、「value」は「グローバル変数」になる。

 

メソッド呼び出し(所属オブジェクト)のパターン

この場合、「this」はメソッド「method」が所属しているオブジェクトである「myObject」を指す。
因みに以下のようにメソッド呼び出しの中で関数呼び出しをした場合でも関数内の「this」はグローバルを指してしまう為、関数呼び出し時の「this.value」は「undefind」となる。

尚、「this」を別の変数(下記の例では「self」)に格納しておくことで、この問題を解決することができる。

 

コンストラクタ呼び出し(インスタンス自身)のパターン

この場合、関数にnew演算子をつけてコンストラクタ呼び出しにてインスタンスを生成している為、「this」は生成されるインスタンス自身となる。
尚、new演算子をつけなかった場合、つまり関数呼び出しとなった場合は、「value」、「increment」共にグローバル変数として定義される。
※慣例として関数をコンストラクタ呼び出しで利用する場合、他の関数と区別しやすくする目的で関数名の先頭を大文字とする。

 

apply/call呼び出し(強制変更)のパターン

この場合、「this」の指すオブジェクトを「newObject」に差し替えてメソッドが呼び出される為、「newObject」の「value」の値が出力される。
「apply/call」を利用すると「強制的にthisの差し替え」ができる。
「apply/call」の第一引数は、「this」に指定したいオブジェクトになる。
尚、「apply」と「call」の違いは、第二引数の指定の仕方になる。

出力結果は、「apply」と「call」共に同じになる。
「apply」は第二引数に配列をとり、配列の中身が引数として渡される。
「call」は、第二引数以降がそのままの形で渡される。

 

JavaScriptにおける継承

JavaScriptのオブジェクトは、他のオブジェクト(またはnull)をプロトタイプとして利用することができる。
オブジェクトのプロパティが参照された際、そのプロパティをオブジェクト自身が保持していなければ、代わりにプロトタイプのプロパティが参照される。
さらにプロトタイプのオブジェクトがそのプロパティを保持していない場合には、プロトタイプのプロトタイプを参照する。(nullに行き着くまで繰り返す)
このようにオブジェクトが他のオブジェクトのプロトタイプとして連鎖していく仕組みをプロトタイプチェーン(prototype chain)と呼ぶ。
JavaScriptでは、このプロトタイプチェーンを利用して継承を行う。

 

コンストラクタ関数とnew演算子のみで継承

まずは、シンプルに基底となるオブジェクト(コンストラクタ)を元に新しいオブジェクト(インスタンス)を生成する方法で継承をしてみる。
※途中に出てくる”arguments”オブジェクトは、関数呼び出しのタイミングで生成されて呼び出し元から与えられた引数の値を配列で保持している特別なオブジェクト

上記のコードで継承を行っている部分は、”Cat.prototype = new Pet();”にあたる。
うまく動作するように見えるが、実際に実行すると”—–Cat—–“の出力の前に”基底オブジェクトのコンストラクタ”が出力される。
これは、”Cat.prototype = new Pet();”のタイミングで基底オブジェクトのコンストラクタが実行されてしまう為である。
これを回避する為には、以下のように継承部分を変更する必要がある。

まず、ダミーのオブジェクト”Func”を定義する。
次に”Func”のプロトタイプに基底オブジェクトのプロトタイプを設定する。
さらに新しいオブジェクト”Cat”のプロトタイプに”Func”から生成したインスタンス(つまり基底オブジェクトのプロトタイプ)を設定する。
最後に新しいオブジェクト”Cat”のプロトタイプに自身のコンストラクタを設定する。
これで継承時に基底オブジェクトのコンストラクタが実行されることがなくなる。

 

Object.createを利用して継承

上記の継承をECMAScript5で定義されたObject.createを利用すると以下のように表すことができる。(新しいオブジェクト”Cat”のほかに別のオブジェクト”Dog”も追加している)

Object.createの第一引数に基底オブジェクトのプロトタイプを、第二引数に新しいオブジェクト自身のコンストラクタを指定して生成したオブジェクトを新しいオブジェクトのプロトタイプに設定している。(メソッドなどを追加する場合も第二引数に列挙する)
Object.createでオブジェクトを生成する際には、コンストラクタは実行されないので継承時に基底オブジェクトのコンストラクタが実行されることはない。

 

JavaScriptでクラスを定義する

JavaScriptでクラス(的なもの)を定義してみる。
プロトタイプベースのオブジェクト指向言語であるJavaScriptには、JavaやC#のようなクラスベースのオブジェクト指向言語とは違い、クラスベースのオブジェクト指向言語でいうところの意味でのクラスは存在しない為、関数オブジェクトをクラスに見立てて疑似的にクラスを定義することになる。
尚、この場でのクラスの定義は「特定のプロパティ(メソッドとメンバ変数)を持つオブジェクト」とする。
※ECMAScript6には、クラス定義が実装されているがここでは触れていない。

 

オブジェクトリテラルでクラスを定義

JavaScript での最も単純なクラスの定義は、オブジェクトリテラルを使用してオブジェクトを定義する形になる。
尚、あるオブジェクトを元に新しいオブジェクトを生成する場合は、ECMAScript5で規定された「Object.create」を利用して元のオブジェクトをプロトタイプとすることで実現できる。

 

コンストラクタでクラスを定義

オブジェクトリテラルではなく、JavaやC#等のクラスベースでのオブジェクト指向のようにクラスを定義する場合は上記のようになる。
そもそもJavaScriptにクラスの概念が無い為、関数定義をnew演算子を使用して呼び出すことでコンストラクタとして働かせ、これをクラスと呼ぶ。
コンストラクタはインスタンスが生成された時に実行される関数のことであり、関数なので引数を与えることもできる。
尚、new演算子を使用しインスタンスを生成するとJavaScriptでは暗黙的に関数内で「this」という空オブジェクトの生成とそのオブジェクトのreturnの2行が追加される。
この為、thisキーワードを使用してメンバ変数を定義することができる。(this.XXX)
また、コンストラクタとしての関数内でthis以外のオブジェクトでreturnを使用するとthisが復帰されなくなってしまう為、コンストラクタとしての関数内ではreturnを使用しない。
その他、メンバ変数に無名関数として処理を代入すれば、インスタンスメソッドとして機能する。
但しこの場合、インスタンスが生成される度にインスタンスが定義され、メモリ領域を圧迫する。
この問題は、prototypeを利用することで解決できる。
※慣例として関数をコンストラクタ呼び出しで利用する場合、他の関数と区別しやすくする目的で関数名の先頭を大文字とする。

 

prototypeを使う

prototypeは、関数オブジェクトが定義されると自動的に生成されるメンバ変数である。
prototypeは、空オブジェクトへの参照を保持しており、インスタンス内に存在しないメンバ変数が参照された場合、prototypeを参照する。
つまり、prototypeのプロパティは関数オブジェクト自身の親への参照のようなものと捉えることができる。

上記の例は、中身が空のクラスを定義し、そのクラスのprototypeに対してmethodプロパティを定義することで、newObjectインスタンスからmethodプロパティを呼び出している。
prototypeは、MyClassクラスの親のようなものであり、prototypeにプロパティを定義することでMyClassのインスタンスであるnewObjectからもmethodを呼び出すことができる。
また、このようにprototypeにクラス共通のプロパティを定義をすることで、インスタンス生成の度にインスタンスメソッドが保持されることが無くなり、メモリ領域の圧迫を軽減できる。

 

改めてJavaScriptでのクラス定義の基本

基本的にJavaScriptでクラスを定義する際には、prototypeに汎用的な関数やクラス共通の変数を定義し、それ以外の変数はメンバ変数としてクラス内に定義することに規定すると理解しやすい。