Function Expression Statements2005年12月10日 21時32分

もじら組フォーラムで書いたことを再掲。以下 JavaScript といった場合 JScript などは含まないものとする。

JavaScript の function

ECMAScript 3 で function キーワードが使われる構文には FunctionDeclarationFunctionExpression があり、以下のように定義されている。

FunctionDeclaration
function Identifier ( FormalParameterListopt ) { FunctionBody }
FunctionExpression
function Identifieropt ( FormalParameterListopt ) { FunctionBody }
FunctionBody
SourceElements
Program
SourceElements
SourceElements
SourceElement
SourceElements SourceElement
SourceElement
Statement
FunctionDeclaration

これをみればわかるとおり、FunctionDeclaration はプログラム直下か関数本体の直下にしか置けない。下の例でいうと (1) は問題ないが (2) は ECMAScript としては正しくないということだ。(2) の場合 gFunctionExpression とみなされるのではと思うかもしれないが、ExpressionStatementfunction キーワードで開始することはできないのでそれも成り立たない。

// (1)
function f() {
  function g() {}
}

// (2)
function f() {
  do {
    function g() {}
  } while (false);
}

しかし、JavaScript ではバージョン 1.5 から Function Expression Statements という機能が追加され、通常の文が置ける場所ならどこでも関数を定義できるようになった (すなわち JavaScript では (2) も正しいコードである) 。ここではこれを仮に FunctionStatement と呼ぶことにする。

FunctionStatement
function Identifieropt ( FormalParameterListopt ) { FunctionBody }

FunctionDeclarationFunctionStatement の最大の違いは処理されるタイミングである。FunctionDeclarationコンパイル時に処理されるその宣言が含まれる関数 (宣言がトップレベルにある場合はプログラム全体) の実行に先立って処理されるFunctionStatement は実行時に処理される。また、FunctionExpression との違いとして、FunctionExpressionIdentifierFunctionBody 内でのみ有効だが、FunctionStatement では文の評価後にその Identifier を使って関数を参照できるようになることが挙げられる。

以上の特徴から、if 文を使った関数定義の振り分けなどもできる。

// (3)
var condition = true;

if (condition) {
  function f() {
    return true;
  }
} else {
  function f() {
    return false;
  }
}

alert(f()); // JavaScript (Firefox など) では true

また、FunctionStatement が実行時に処理されるということは、その文の評価前はその関数を参照できないということでもある。以下のコードは JavaScript では例外が投げられる。

// (4)
try {
  f();
  function f() {
    alert("Succeed!");
  }
} catch (e) {
  alert(e); // ReferenceError: f is not defined
}

なお、FunctionStatementIdentifier が存在する場合、FunctionStatement の文としての返り値は空値 (empty) になるが、Identifier が存在しない場合はその関数オブジェクトが返る。ただし Firefox 1.0 では Identifier を省略できない (省略するとシンタックスエラーになる) 。

JScript の function

だが、Function Expression Statements はあくまでも JavaScript だけの機能であり、JScript などでは実装されていない。しかし、JScript や Opera の ECMAScript 実装でも任意の場所に function 文を置くことができる。ただし、それが処理されるのはコンパイル時であり、JavaScript のような動的な関数定義はできない。それが証拠に IE や Opera で上記 (3) のコードを実行してみると false と表示される。

さらに、JScript の場合は、FunctionExpression さえもコンパイル時に処理し、その Identifier を変数オブジェクトに登録してしまう。結果以下のコードで "Hello" と表示されてしまうのである。これは明らかに ECMAScript に違反している。

// (5)
alert(f()); // "Hello"
var g = function f() { return "Hello"; };
alert(f == g); // false