関数と実行

最初に注意

自分はつい最近JavaScriptをかじったばかりの,ただの初級者です.以下の文章は単に自分はこう考えている,というだけで,誤りが含まれている可能性は十分にあります.もし読まれる方がいたら,その点を十分にご理解ください.また誤りの指摘は大歓迎です.(以上、コピペ)
特に今回は,途中から妄想というか,思考実験モードになっています.うっかり信じると痛い目をみるので注意してください

Callオブジェクト

これまで自分の常識では,関数の引数はスタックに積まれるもの,という認識でした.つまり関数の入場動作として引数をスタックにプッシュし,退場動作で引数をスタックからポップします.
JavaScriptでは,引数(及びローカル変数)をスタックではなくCallオブジェクトで管理します.Callオブジェクトもオブジェクトですから,実際には(おそらく)Callオブジェクトが持つ連想配列に格納されます.Callオブジェクトは不要になった時点(ほとんどの場合関数の実行が終了した時点)でガベージコレクタによって回収されます.

関数とメソッド

一般的なクラスベースのオブジェクト指向言語(C++/Java/C#など)には,thisという概念があります.インスタンスメソッド内でthisは,そのインスタンス自身を指します.JavaScriptにもthisがあり概念は一緒です.ただ実現方法が特殊でとても興味深いです.
JavaScriptではthisは暗黙の引数として関数に渡され,Callオブジェクトに登録されます.ここで疑問が生じます.JavaScriptではよく,トップレベルで関数を作成し,実行させています.その場合のthisとは何でしょうか?
実はJavaScriptではオブジェクトに紐付かない関数というのはありません.トップレベルの関数はグローバルオブジェクトのメソッドになります.Webでは,windowオブジェクトがグローバルオブジェクトになります.

関数オブジェクト

よく言われているように,JavaScriptでは関数もオブジェクトです.関数の作成方法は3つあります.

function sayHello(name)
{
  return "Hello, " + name + " !"; 
}
var sayHello = new Function(name, " return 'Hello, ' + name + ' !'; ");
var sayHello = function(name){ return "Hello, " + name + " !"; };

1番目が,もっとも一般的な関数の定義方法です.2番目と3番目は,= の右辺で関数オブジェクトを作成し,作成したオブジェクトに左辺の変数を束縛します.このように関数はオブジェクトなので,関数の引数にしたり戻り値にしたりと,いわゆる高階関数を作ることが出来ます.高階関数の引数・戻り値に指定する関数は,コンストラクタとして使用することも出来ますから,その場合はジェネリクスに近い使い方も可能です.

思考実験: 関数の実行について

さて,関数オブジェクトもオブジェクトです.ということは,関数オブジェクトとオブジェクトはis-a関係なのでしょうか?
もしis-a関係だとすると,オブジェクトと関数オブジェクトの間の差分はなんでしょうか?それは機能レベルで見た場合,関数は実行できるがオブジェクトは実行できない,ということだと思います.それでは,実行する,とはどういう概念なのでしょうか?言い換えると,Objectにどういった差分機能があれば実行できるようになるのでしょうか?
この疑問について,自分はまだ満足できる解説を見たことがありません.ここから先,思考実験してみます.


関数の定義と実行のサンプルを以下に示します.

var sayHello = new Function(name, " return 'Hello, ' + name + ' !'; ");
var hello_string = sayHello('kurip');

まずは関数の定義です.関数の定義の仕方は3通りありましたが,本質的なのはこの記述方式で,残りはシンタックスシュガーであると仮定します.Functionコンストラクタの引数として,関数のパラメータ部分と本体を渡しています.ところでこの引数は,Callオブジェクトに格納されるはずですね.おそらく連想配列に格納されると仮定しましょう.
連想配列にどのように格納されるでしょうか?nameはKeyとして登録されて,関数実行時にvalueが設定される感じでしょう.一方関数本体のほうは,Valueのほうに設定されるほうが自然ですね.するとKeyの名前をどうするかという問題になりますが,仮に[anonymous]というKey名だと仮定します.
ここまでがFunctionコンストラクタをnewしたときの動作になります.箇条書きでまとめると

  1. Callオブジェクト生成
  2. Callオブジェクトにnameプロパティ作成.プロパティ値はなし(undefined)
  3. Callオブジェクトに[anonymous]プロパティ作成.プロパティ値は関数本体をあらわす文字列

次に関数の実行です.関数オブジェクトは()演算子が使えますが,それを解決する仕組みは言語レベルで存在するとしてここでは考えないことにします.()演算子の中のパラメータは,Callオブジェクトに設定します.例の場合には,Callオブジェクトのnameプロパティのvalueに'kurip'が設定されます.ちなみにJavaScriptでは定義時の引数の数と実行時の引数の数を一致させる必要はありません.たとえばsayHello関数実行時に2番目の引数で電話番号'090-xxxx-xxxx'を設定することが出来ます.このような状況に対応するために,Callオブジェクトにはargumentsプロパティが存在します.argumentsプロパティはArgumentsオブジェクトと呼ばれる,パラメータを配列で管理するオブジェクトを参照します.したがって,'kurip'はCallオブジェクトのnameプロパティのvalueで参照できる以外にarguments[0]でも参照できます.電話番号'090-xxxx-xxxx'はarguments[1]でしか参照できません.
ここまで,関数実行時の動作を箇条書きにします.

  1. (言語レベルで()演算子を解決)
  2. Argumentsオブジェクトを生成し,ArgumentsオブジェクトにCallオブジェクトのargumentsプロパティを束縛
  3. 1番目の引数'kurip'をCallオブジェクトのnameプロパティの値に設定.と同時に,Callオブジェクトのarguments[0]に設定
  4. 2番目以降の引数がある場合,Callオブジェクトのarguments[1]以降に順次設定

ここまでが関数実行のスタートアップ処理で,あとは関数本体を実行します.関数本体はCallオブジェクトの[anonymous]プロパティに文字列として保存されています.これをどのように実行するか?ヒントがあります.eval関数です.
eval関数は普段数値の四則演算なんかで使うことが多いと思いますが,式の評価(evaluate)にも使えます.たとえばこんな感じ.

document.write(eval("var sayHello = new Function(\"name\", \"return name + ', Hello!'; \"); sayHello('kurip')"));

今回の例では,[anonymous]プロパティに" return 'Hello, ' + name + ' !'; "という文字列があって,eval関数の中でnameが解決できればいけそうな気がする?と思ったんですが,そう簡単にはいきません.というのも,どうもeval関数はreturn文と相性が悪いらしく,コンパイルエラーになってしまうんですね.
そんなわけで,eval関数そのものではダメですが,それに類する関数・機能があると仮定して,JavaScriptにおける関数の実行ってこんな感じかな?という思考実験でした.もっとも仮定の上に仮定を重ねているので,実際には全く違ってるかもしれません.

まとめ -- 今後の方針

ここまで,JavaScriptにおけるオブジェクト指向の考え方とか関数の概念について,勉強しながら感じたことを臆面もなく書いてみました.系統だって書いてないし,推敲が不十分なので論理の筋道がこんがらがっている気がしますが,それなりに真面目に,正直に,全力で書いたつもりです(なので,書き方・内容が悪いのは単に自分の力不足です).
これを書いた動機は,書くことによってJavaScriptの理解が深まればいいなということでした.こんな日記でも,なにかを書こうとするとそう変なことは書けないわけで,とても勉強になった気がします.
JavaScriptに関しては,今回のCallオブジェクトに関係の深いクロージャやレキシカルスコープといったさらに奥の深い概念があるのですが,これらについて説明するにはあまりに自分は力不足なので,日記で説明するのはこのへんで止めておきます.
以前,JavaScriptの勉強の計画を立てたとき,まずはJava Scriptのコア部分を勉強し,その後JSON/prototype.jsといったあたりを攻めようと考えていました.実はこの辺の勉強も進んでいて,JSONについては,概念的に,まぁOK! prototype.jsはいちおう勉強する準備は出来ました.とはいえ,今後は得られた知識からどのようなOutputを出していくか?ということも考えていく必要もあるので,prototype.jsはほどほどに,ASP.NET AJAXに切り替えていくつもりです.ASP.NET AJAXは先発のライブラリと比べるとまだまだ情報が少ない気がするので,食い込む余地は残っているかな?とかしょうもないことを考えています.