JavaScriptの関数を利用する場合に、もう1つ忘れてはならないトピックとして、argumentsオブジェクトがある。argumentsオブジェクトは、関数の内部でのみ利用可能なオブジェクトで、関数に渡された引数値を管理することができる。
■JavaScriptは引数をチェックしない
argumentsオブジェクトは、具体的にどのような局面で利用すればよいのか ―― それを解説する前に、まずは以下のコードをご覧いただきたい。
function display(msg) {
window.alert(msg);
}
display(); // undefined [A]
display('山田'); // 「山田」と表示 [B]
display('山田', '掛谷'); // 「山田」と表示 [C]
display関数自体はごく単純なもので、引数msgに与えられた文字列をそのままダイアログ表示するための関数だ。このdisplay関数を、それぞれ引数0、1、2個を指定した形で呼び出してみるとどうだろう。
通常のVisual BasicやC#のような言語に慣れている方にとっては、関数のシグニチャと正しく合致した呼び出し以外はエラーとなるのが、直感的に正しい挙動だ。しかし、JavaScriptではそうはならない。いずれも正しく呼び出せてしまうのだ。
[B]が正しく動作するのは当然として、[A]では引数が与えられないため「undefined」(未定義)が、[C]では、多かった引数は(取りあえず)無視されて[B]と同じ結果を得ることができる。
この結果から分かることは、JavaScriptが「シグニチャ(データ型の概念がないので、要は引数の数)をチェックしない」という点だ。とすると、想定していた数以上の引数が与えられた[C]のようなケースでは、「多すぎた引数」はただ切り捨てられてしまうだけなのだろうか。
否。ここで登場するのが、argumentsオブジェクトだ。JavaScriptでは関数が呼び出されたタイミングで内部的にargumentsオブジェクトが生成され、呼び出し元から渡された変数を格納する。このargumentsオブジェクトを利用することで、(例えば)関数に与えられた引数の数をチェックすることも可能だ。
[参考]argumentsオブジェクトの実体
厳密には、JavaScriptは関数呼び出しのタイミングで、ローカル変数や引数情報を、Activation Object(通称、「Callオブジェクト」とも呼ばれる)のプロパティとして格納している。argumentsオブジェクトも、その実体はCallオブジェクトのargumentsプロパティであある。
Callオブジェクトは、アプリケーション側から明示的に生成したり呼び出したりすることはできないし、通常は意識することすらない存在であるので、本稿ではただ単に「argumentsオブジェクト」と呼ぶものとする。
以下のコードは、先ほどのdisplay関数に引数の個数チェックを加えたものである。
function display(msg) {
if (arguments.length == 1) {
window.alert(msg);
} else {
window.alert('引数の数が正しくありません。');
}
}
display(); // 「引数の数が正しくありません。」を表示
display('山田'); // 「山田」を表示
display('山田','掛谷'); // 「引数の数が正しくありません。」を表示
argumentsオブジェクトは、通常の配列オブジェクトと同様にlengthプロパティを公開しており、これにより配列に含まれる要素数(ここでは関数に実際に渡された引数の数)を取得することができる。
ここではlengthプロパティを確認して、その値が1以外である場合(引数の数が1個でない場合)、エラー・ダイアログを表示するようにしているわけだ。同様に、argumentsオブジェクトの中身を確認することで、引数のデータ型や値の妥当性などを確認し、関数内部の処理で予期せぬエラーを未然に防ぐような処理も可能だろう。
もっとも、このようなargumentsオブジェクトによる引数の妥当性チェックは、Visual BasicやC#などの世界ではコンパイラが自動で行ってくれるもので、JavaScriptでは自分で実装しなければならないのは、むしろデメリットにも思われる。
■argumentsオブジェクトによる可変個引数の関数宣言
しかし、argumentsオブジェクトにはもう1つ便利な(そして重要な)使い方がある。それが可変個引数の関数宣言だ。
可変個引数の関数とは、(あらかじめ指定された個数の引数ではなく)任意の数の引数を与えられる関数のこと。宣言時に引数の個数を特定できないような関数を定義したい場合に、利用することができる。
例えば、以下では引数に与えられた任意の数の数値の平均値を求めるaverage関数を定義してみることにしよう。
function average() {
var sum=0;
for (i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum / arguments.length;
}
window.alert(average(10, 20, 30, 40)); // 「25」を表示
ここではforループの中でargumentsオブジェクトからすべての要素を取り出し、その合計値を要素数で除算している。argumentsオブジェクトから個々の要素にアクセスする場合も、通常の配列と同様に「arguments[i]」のように記述すればよい。
ここでは、すべての引数を名前を持たない引数として扱っているが、もちろん、一部の引数は通常の名前付き引数として明示的に宣言しておき、その後方に名前なし引数を持たせるようなことも可能だ。例えば、以下のようなコードを見てほしい。
function format(fmt) {
for (i = 1; i < arguments.length; i++) {
var reg = new RegExp("\\{" + (i - 1) + "\\}", "g")
fmt = fmt.replace(reg,arguments[i]);
}
return fmt;
}
var dat = new Date();
window.alert(
format("今日は{0}年{1}月{2}日です",
dat.getFullYear(), dat.getMonth() + 1, dat.getDate())
); // (例えば)「2007年7月5日」を表示
format関数は、第1引数に与えられた書式に含まれるプレイスホルダ({0}、{1}、{2}……)を第2引数以降の対応する文字列で置き換えた結果を返すための関数だ(.NET FrameworkライブラリのString.Formatメソッドに相当するものだと考えれば分かりやすいだろう)。
ここで注目していただきたいのは、format関数では第1引数fmtを名前付き引数として指定しておき、第2引数以降を名前なし引数として定義している点だ。前述したように、JavaScriptでは一部の引数を明示的に名前付き引数として宣言することも可能だ。
ただしこの場合も、argumentsオブジェクトには、名前付き引数/名前なし引数の双方が「すべて」含まれているという点に注意してほしい。
従って、この場合、引数fmtは「arguments[0]」としてもアクセスすることが可能だ*3。そして、名前なし引数の値は「arguments[1]」以降に格納されることになる。ここでは、forループで2番目の要素(インデックスは1)から末尾の要素までを順番に取り出し、対応するプレイスホルダ({0}、{1}、{2}……)との置き換え処理を行っているというわけだ。
*3 つまり、format関数のような例でも、すべての引数を名前なし引数としてしまうことが構文上は可能である。しかし、コードの可読性を考慮した場合、すべてをargumentsオブジェクトに委ねるのではなく、固定(名前付き)で宣言できる引数は明示的に宣言しておくのが好ましい。
■現在実行中の関数を参照する − calleeプロパティ −
最後に、argumentsオブジェクトが公開している重要なプロパティとして、calleeプロパティを紹介し、今回の締めくくりとすることにしよう。
calleeプロパティは現在実行中の関数を参照するためのプロパティだ。さっそく、具体的なコード例を見てみることにしよう。以下のコードは、再帰処理によって、与えられた数値の階乗(例えば4の階乗は4X3X2X1=24)を求めるfactorial関数を定義したものだ。
function factorial(n) {
if (n == 0) {
return 1;
} else {
return n * arguments.callee(n - 1);
}
}
alert(factorial(4)); // 24を表示
再帰処理に際して、arguments.calleeプロパティで自分自身を呼び出しているのが確認できるはずだ。もっとも、この例であれば、あえてcalleeプロパティを使わなくても、シンプルに、
return n * factorial(n - 1);
と関数名を指定すればよいと思われるかもしれない。
確かに、この場合は問題ない。しかし、関数が匿名関数の場合はどうだろう。再帰処理に際して呼び出すべき名前がないので、この場合は、calleeプロパティを利用する必要があるというわけだ。
var factorial = function(n) {
if (n == 0) {
return 1;
} else {
return n * arguments.callee(n - 1);
}
}
再帰処理は繰り返し構造を表現するうえで欠かせない初歩的な技術でもあり、calleeプロパティはこの再帰処理を匿名関数とともに表現するうえで欠かせない機能であるので、ぜひとも押さえておいていただきたい。
以上、今回はJavaScriptにおける関数の基本的な構文と、関連する匿名関数や高階関数、argumentsオブジェクトの概念とその使い方について紹介した。次回は、変数のデータ型やスコープについて紹介する予定だ。ご期待いただきたい。
Copyright© Digital Advantage Corp. All Rights Reserved.