■Functionコンストラクタによる定義
関数がオブジェクトであるというならば、もしかしたらnew演算子で関数を宣言できるのではないか ―― しかり。JavaScriptでは、確かにFunctionコンストラクタとnew演算子を用いることで関数を定義することが可能だ。
前述(リスト3)のadd関数を、Functionコンストラクタで定義すると、以下のように記述できる。
var add = new Function("x", "y", "return x + y");
Functionコンストラクタでは、任意の数の引数を指定することが可能だ。末尾の引数を除くすべての引数は、関数に引き渡すべき引数を表すものだ。そして、末尾の引数が関数の本体を表す。ここでは関数本体が単一の文から構成される例を示しているが、文が複数存在する場合には、通常の関数定義と同様、セミコロン(;)で文を区切ればよい。
また、引数(ここではxとy)は、以下のようにカンマ区切りでまとめて指定することも可能だ。
var add = new Function("x, y", "return x + y");
もっとも、先のfunction文を使わずに、あえてFunctionコンストラクタを利用するメリットは見えにくいかもしれない。シンプルにfunction文を利用した方がコードもすっきりと見やすいのではないかと考える向きもあるだろう。しかし、Functionコンストラクタには、標準のfunction文にはない重要な特徴がある。
というのも、Functionコンストラクタでは関数の本体部分を文字列として指定できるという点だ。このため、スクリプト内で文字列操作を行うことで、実行時に動的に関数の挙動を変更することができる。
例えば、以下のコードはcalc関数をFunctionコンストラクタで定義する、ごくシンプルなコードだ。
var ope = "-";
var calc = new Function("x", "y", "return x" + ope + "y;")
window.alert(calc(2, 3)); // 「-1」を表示
ここで注目していただきたいのは、Functionコンストラクタの末尾の引数 ―― 関数本体が変数opeの値によって動的に生成されている点だ。ここでは変数opeの値を固定的に指定しているだけであるが、もちろん、条件に応じて変数opeの値を切り替えることも可能だ。
ただし、ここで1つ注意しなければならないポイントが出てくる。というのも、「実行時に動的に関数の挙動を変更できる」ということは、Functionコンストラクタではfunction文とは異なり、「関数が静的な構造として組み込まれるわけではない」ということだ。
例えば、以下のコードは、前項最後のリスト6をFunctionコンストラクタで書き換えたものだ。
window.alert(add(5, 7)); [A]
var add = new Function("x", "y", "return x + y");
一見、リスト6と同じ処理をしているように見えるが、このコードはエラーとなる。[A]の段階では、まだ変数addに関数オブジェクトがセットされていないためだ。このことからもFunctionコンストラクタが実行時に評価されていることが分かるだろう。
ちなみに、同じ理由から、Functionコンストラクタはfor/whileなどのループ内、または頻繁に呼び出される関数内で使用するべきではない。Functionコンストラクタは、実行時に呼び出されるたびに新たな関数オブジェクトを生成するため、実行速度低下の原因となるからだ。
[コラム]JavaScriptの識別子
変数や関数の名前(識別子)を決める場合には、定められた識別子の規則に従っておく必要がある。この規則は以下のとおりだ。
ただし、JavaScriptで固有の意味を持つ予約語は、識別子として利用することはできないので注意すること。JavaScriptの予約語は以下のとおりだ。
break | case | catch | continue | default | delete | do | |
else | finally | for | function | if | in | instanceof | |
new | return | switch | this | throw | try | typeof | |
var | void | while | with | ||||
JavaScriptの予約語 |
また、将来的に予約語として採用される可能性がある以下のようなキーワードも識別子として避けた方がよいだろうし、JavaScriptですでに定義されているオブジェクトやそのメンバ名(例えば、ArrayやNumber、Objectなど)も、特別な理由がない限り、識別子として利用するのは避けるべきだろう(すでに定義されている名前で新たな変数や関数を定義した場合、もともとの機能は利用できなくなる)。
abstract | boolean | byte | char | class | const | debugger | |
double | enum | export | extends | final | float | goto | |
implements | import | int | interface | long | native | package | |
private | protected | public | short | static | super | synchronized | |
throws | transient | volatile | |||||
将来的に予約語となる可能性があるキーワード |
■関数リテラルによる定義
そして、関数を定義する3番目の手段が「関数リテラル」だ。関数リテラルを使って、先ほどのadd関数定義を書き直してみると、以下のように記述できる。
var add = function(x, y) { return x + y; };
function文を利用した1番目の記法(リスト3)に似ていると思われるかもしれないが、いくつかの違いもあるので注目だ。
まず、function文では関数addを直接に定義しているのに対して、関数リテラルでは「function(x, y) ……」と名前のない関数を定義したうえで、これを変数addに格納している。関数リテラルは、宣言する時点では名前を持たないことから「無名関数」、または「匿名関数」と呼ばれる場合もある。
また、function文は静的な構造を宣言するものであるのに対して、関数リテラルは式として使用される。つまり、function文よりも柔軟性のある記述が可能だ。この点は重要なポイントであるので、あらためて次の項で後述する。
[参考]匿名関数定義における関数リテラルとFunctionコンストラクタの違い
宣言時に名前が必要ないという意味では、前述したFunctionコンストラクタも同様だ。しかし、Functionコンストラクタが文字列で関数本体を定義しなければならないことから記述が冗長になりやすいのに対して、関数リテラルはJavaScriptの標準的な構文で記述でき、コードが読みやすいというメリットがある。通常、匿名関数を定義するには、(Functionコンストラクタではなく)関数リテラルを使用するのが好ましい。
また、厳密にはFunctionコンストラクタと関数リテラルとでは、関数解釈の挙動が異なる場合もあるので、注意が必要だ。この違いについては、あらためて次回「変数の宣言とスコープ」で紹介の予定だ。
Copyright© Digital Advantage Corp. All Rights Reserved.