関数関数 は、特定のタスクを実行し、プログラム内で再利用できるコードブロックです。 ActionScript 3.0 には、メソッドと関数クロージャの 2 種類の関数があります。関数をメソッドと呼ぶか関数クロージャと呼ぶかは、関数が定義されたコンテキストによって決まります。 関数をクラス定義の一部として定義した場合、またはオブジェクトのインスタンスに関連付けた場合は、メソッドと呼びます。 関数がその他の方法で定義された場合は、関数クロージャと呼びます。 ActionScript では、関数は非常に重要です。 ActionScript 1.0 では、例えば、class キーワードが存在しなかったので、「クラス」はコンストラクター関数で定義されました。その後、class キーワードが追加されましたが、ActionScript をフルに活用するには、関数について理解しておくことが重要です。しかし、これは、ActionScript の関数が C++ や Java などの言語の関数と同じように動作することを期待するプログラマーにとっては難しい場合があります。 経験豊富なプログラマーにとっては基本的な関数の定義や呼び出しは問題がありませんが、ActionScript の関数の高度な機能の中には説明が必要なものもあります。 基本的な関数の概念関数の呼び出し関数を呼び出すには、識別子の後に括弧(())を使用します。関数に渡す関数パラメーターを括弧で囲みます。 例えば、trace() 関数は ActionScript 3.0 のトップレベルの関数です。 trace("Use trace to help debug your script"); 関数をパラメーターなしで呼び出す場合、空の括弧のペアを使用する必要があります。 例えば、パラメーターを取らない Math.random() メソッドは、乱数を生成します。 var randomNum:Number = Math.random(); 独自の関数の定義ActionScript 3.0 の関数を定義するには、function ステートメントを使用する方法と関数式を使用する方法の 2 つがあります。 どちらの方法を選択するかは、プログラミングスタイルをより静的にするか動的にするかによって決まります。 静的な、つまり strict モードのプログラミングの方を好む場合は、function ステートメントで関数を定義します。 そうすることに特定の必要性がある場合は、関数式で関数を定義します。 関数式は、動的、つまり standard モードのプログラミングでより頻繁に使用されます。 function ステートメントfunction ステートメントは、strict モードで関数を定義するのに適しています。 function ステートメントは、function キーワードで始まり、次のアイテムが続きます。
例えば、次のコードは、パラメーターを定義する関数を作成し、ストリング「hello」をパラメーター値として使用して関数を呼び出します。 function traceParameter(aParam:String) { trace(aParam); } traceParameter("hello"); // hello 関数式関数を宣言する 2 つ目の方法は、代入ステートメントに関数式を使用することです。関数式は、関数リテラルまたは匿名関数とも呼ばれます。 これは、旧バージョンの ActionScript で広く使用されている、より冗長になる方法です。 関数式を持つ代入ステートメントは、var キーワードで始まり、次のアイテムが続きます。
ステートメントと式の選択原則として、式を使用する必要がある場合を除いて、function ステートメントを使用します。 function ステートメントは、関数式より簡潔で、strict モードと standard モードで一貫した使いやすさを提供します。 function ステートメントは、関数式を含む代入ステートメントより読みやすくなります。 function ステートメントを使用すると、コードが簡潔になり、var と function キーワードを両方使用する必要がある関数式よりわかりやすくなります。 function ステートメントは、2 つのコンパイラーモードで一貫した使いやすさを提供します。つまり、strict モードおよび standard モードの両方でドットシンタックスを使用し、function ステートメントを使用して宣言されたメソッドを呼び出すことができます。これは、関数式を使用して宣言されたメソッドには必ずしも当てはまりません。 例えば、次のコードは、2 つのメソッドで Example というクラスを定義します。関数式で宣言される methodExpression() と function ステートメントで宣言される methodStatement() です。strict モードでは、ドットシンタックスを使用して、methodExpression() メソッドを呼び出すことはできません。 class Example { var methodExpression = function() {} function methodStatement() {} } var myEx:Example = new Example(); myEx.methodExpression(); // error in strict mode; okay in standard mode myEx.methodStatement(); // okay in strict and standard modes 関数式は、実行時、つまり動的なビヘイビアーを中心にしたプログラミングに適しています。 strict モードを使用し、関数式で宣言されるメソッドを呼び出す必要がある場合は、次の 2 つの方法のいずれかを使用することができます。 1 つ目は、ドット(.)演算子ではなく、角括弧([])を使用してメソッドを呼び出す方法です。次のメソッドの呼び出しは strict モードと standard モードの両方で成功します。 myExample["methodLiteral"](); 2 つ目は、クラス全体をダイナミッククラスとして宣言することができます。 この場合、ドット演算子を使用してメソッドを呼び出すことができますが、そのクラスのすべてのインスタンスで strict モードの一部の機能が犠牲になるという短所があります。 例えば、ダイナミッククラスのインスタンスの未定義のプロパティにアクセスしようとした場合、コンパイラーはエラーを生成しません。 関数式が便利な場合があります。 関数式の一般的な使用法は、1 回だけ使用された後で破棄される関数に使用することです。 また、一般的な使用法ではありませんが、関数をプロトタイププロパティに関連付けるために使用します。 詳しくは、プロトタイプオブジェクトを参照してください。 function ステートメントと関数式には、どちらを使用するかを選択する際に考慮する必要がある微妙な違いが 2 つあります。 1 つ目の違いは、関数式は、メモリ管理およびガベージコレクションに関してオブジェクトとして単独で存在しません。 つまり、配列エレメントやオブジェクトプロパティなどの別のオブジェクトに関数式を割り当てると、コード内にその関数式への唯一の参照が作成されます。 関数式が関連付けられている配列またはオブジェクトがスコープ外に移動するか、使用できなくなった場合、関数式にアクセスできなくなります。配列またはオブジェクトが削除されると、関数式が使用するメモリがガベージコレクションの対象となります。つまり、メモリは他の目的で再要求され、再利用される対象となります。 次の例では、関数式の場合、関数式が割り当てられているプロパティが削除されると、関数が利用できなくなることを示します。 クラス Test は動的です。つまり、関数式を保持する functionExp というプロパティを追加できます。functionExp() 関数は、ドット演算子を使用して呼び出すことができますが、functionExp プロパティが削除されると、関数にアクセスできなくなります。 dynamic class Test {} var myTest:Test = new Test(); // function expression myTest.functionExp = function () { trace("Function expression") }; myTest.functionExp(); // Function expression delete myTest.functionExp; myTest.functionExp(); // error その一方で、関数が最初に function ステートメントで定義された場合、関数はそのオブジェクトとして存在し、関連付けられているプロパティを削除しても存在します。 delete 演算子はオブジェクトのプロパティでのみ動作するので、関数 stateFunc() 自体を削除する呼び出しであっても動作しません。 dynamic class Test {} var myTest:Test = new Test(); // function statement function stateFunc() { trace("Function statement") } myTest.statement = stateFunc; myTest.statement(); // Function statement delete myTest.statement; delete stateFunc; // no effect stateFunc();// Function statement myTest.statement(); // error function ステートメントと関数式の 2 つ目の違いは、function ステートメントは、関数ステートメントの前に現れるステートメント内を含む、定義されたスコープ全体で存在することです。 対照的に、関数式はそれ以降のステートメントに対してのみ定義されます。 例えば、次のコードは、定義される前に scopeTest() 関数を正常に呼び出します。 statementTest(); // statementTest function statementTest():void { trace("statementTest"); } 関数式は定義されるまで使用できないので、次のコードはランタイムエラーを発生します。 expressionTest(); // run-time error var expressionTest:Function = function () { trace("expressionTest"); } 関数からの戻り値関数から値を返すには、return ステートメントの後に、返す式またはリテラル値を使用します。例えば、次のコードは、パラメーターを表す式を返します。 function doubleNum(baseNum:int):int { return (baseNum * 2); } return ステートメントは関数を終了させるので、次のように return ステートメントの下にあるステートメントは実行されないことに注意してください。 function doubleNum(baseNum:int):int { return (baseNum * 2); trace("after return"); // This trace statement will not be executed. } strict モードでは、戻り値の型を指定するように選択した場合、適切な型の値を返す必要があります。 例えば、次のコードは有効な値を返さないので、strict モードではエラーが発生します。 function doubleNum(baseNum:int):int { trace("after return"); } ネストされた関数関数をネスト、つまり別の関数内で関数を宣言することができます。 ネストされた関数は、関数への参照が外部コードに渡される場合を除いて、その親関数内でのみ使用できます。 例えば、次のコードは getNameAndVersion() 関数内に 2 つのネストされた関数を宣言します。 function getNameAndVersion():String { function getVersion():String { return "10"; } function getProductName():String { return "Flash Player"; } return (getProductName() + " " + getVersion()); } trace(getNameAndVersion()); // Flash Player 10 ネストされた関数が外部コードに渡される場合、関数クロージャとして渡されます。つまり、関数が定義されるとき、スコープ内にある定義を保持します。 詳しくは、関数のスコープを参照してください。 関数パラメーターActionScript 3.0 は、ActionScript を初めて使用するプログラマーには斬新な関数パラメーターの機能を備えています。 パラメーターの値渡しと参照渡しは、ほとんどのプログラマーにとって馴染みのある概念ですが、arguments オブジェクトと ...(残り引数)パラメーターは使用したことがないプログラマーも多いと思われます。 引数の値渡しと参照渡し多くのプログラミング言語では、値渡しと参照渡しによるパラメーターの受け渡しの違いを理解しておくことが重要です。この違いは、コードの設計方法に影響します。 値渡しとは、関数内で使用するためにパラメーターの値がローカル変数にコピーされることです。 参照渡しとは、実際の値ではなく、パラメーターへの参照のみが渡されることです。 実際の値がコピーされるのではなく、 パラメーターとして渡される、変数への参照が作成され、関数内で使用するためにローカル変数に割り当てられます。 関数外の変数への参照では、ローカル変数は元の変数の値を変更する機能を提供します。 ActionScript 3.0 では、値はすべてオブジェクトとして格納されているため、すべてのパラメーターは参照渡しです。 しかし、Boolean、Number、int、uint、String などのプリミティブデータ型に属するオブジェクトには、値渡しのように動作する特別な演算子があります。 例えば、次のコードは、xParam と yParam という 2 つの int 型パラメーターを定義する passPrimitives() という関数を作成します。この 2 つのパラメーターは、passPrimitives() 関数の本体内で宣言されるローカル変数に似ています。関数がパラメーター xValue および yValue で呼び出されると、パラメーター xParam および yParam は、xValue と yValue で表される int オブジェクトへの参照で初期化されます。 パラメーターはプリミティブなので、値渡しのように動作します。 xParam と yParam は、初期状態では xValue と yValue オブジェクトへの参照のみが含まれますが、関数本体内の変数の変更によりメモリ内に新しい値のコピーが生成されます。 function passPrimitives(xParam:int, yParam:int):void { xParam++; yParam++; trace(xParam, yParam); } var xValue:int = 10; var yValue:int = 15; trace(xValue, yValue);// 10 15 passPrimitives(xValue, yValue); // 11 16 trace(xValue, yValue);// 10 15 passPrimitives() 関数内では、xParam および yParam の値はインクリメントされますが、最後の trace ステートメントに示されているように、これは xValue および yValue の値に影響を与えません。パラメーターが変数 xValue および yValue と同じ名前である場合でも同じです。これは、関数内の xValue および yValue は関数の外部にある同じ名前の変数とは別に存在するメモリ内の新しい位置を参照するためです。 プリミティブデータ型に属さないその他すべてのオブジェクトは、常に参照によって渡され、元の変数の値を変更する機能を提供します。例えば、次のコードは、2 つのプロパティ x および y を持つ objVar というオブジェクトを作成します。このオブジェクトは、passByRef() 関数にパラメーターとして渡されます。オブジェクトはプリミティブ型ではないため、参照渡しで渡されるだけではなく、参照のままでもあります。 つまり、関数内のパラメーターを変更すると、関数の外側にあるオブジェクトのプロパティも影響を受けます。 function passByRef(objParam:Object):void { objParam.x++; objParam.y++; trace(objParam.x, objParam.y); } var objVar:Object = {x:10, y:15}; trace(objVar.x, objVar.y); // 10 15 passByRef(objVar); // 11 16 trace(objVar.x, objVar.y); // 11 16 objParam パラメーターは、グローバル変数 objVar と同じオブジェクトを参照します。この例の trace ステートメントからもわかるように、objParam オブジェクトの x および y プロパティを変更すると、objVar オブジェクトにも反映されます。 デフォルトのパラメーター値ActionScript 3.0 では、関数にデフォルトのパラメーター値を宣言できます。デフォルトのパラメーター値を使用した関数の呼び出しでデフォルト値のパラメーターが省略されると、そのパラメーターの関数定義で指定された値が使用されます。 デフォルト値のパラメーターはすべてパラメーターリストの末尾に配置する必要があります。 デフォルト値として割り当てられた値はコンパイル時定数である必要があります。 パラメーターのデフォルト値が存在すると、そのパラメーターはオプションパラメーターになります。デフォルト値を持たないパラメーターは、必須パラメーターと見なされます。 例えば、次のコードは、3 つのパラメーターで関数を作成します。このうち、2 つのパラメーターにはデフォルト値があります。 パラメーター 1 つだけで関数を呼び出す場合、そのパラメーターのデフォルト値が使用されます。 function defaultValues(x:int, y:int = 3, z:int = 5):void { trace(x, y, z); } defaultValues(1); // 1 3 5 arguments オブジェクトパラメーターが関数に渡されると、arguments オブジェクトを使用して関数に渡されたパラメーターについての情報にアクセスできます。arguments オブジェクトには、次のようないくつかの重要な側面があります。
...(rest) パラメーターActionScript 3.0 は、...(rest) パラメーターと呼ばれる新しいパラメーター宣言を導入しています。 このパラメーターを使用すると、任意の数のカンマ区切りのパラメーターを受け入れる配列パラメーターを指定できます。 パラメーターには、予約語ではない名前を指定することができます。 このパラメーター宣言は、指定される最後のパラメーターである必要があります。 このパラメーターを使用すると、arguments オブジェクトにアクセスできなくなります。 ... (rest) パラメーターには arguments 配列および arguments.length プロパティと同じ機能がありますが、arguments.callee のような機能はありません。... (rest) パラメーターを使用する前に、arguments.callee を使用する必要はありません。 次の例は、arguments オブジェクトの代わりに ... (rest) パラメーターを使用して、traceArgArray() 関数を書き直したものです。 function traceArgArray(... args):void { for (var i:uint = 0; i < args.length; i++) { trace(args[i]); } } traceArgArray(1, 2, 3); // output: // 1 // 2 // 3 ... (rest) パラメーターは、最後にパラメーターとしてリストする限り、他のパラメーターと共に使用することもできます。 次の例は、最初のパラメーター x を int 型にし、2 つ目のパラメーターとして ... (rest) パラメーターを使用するように、traceArgArray() 関数を変更します。最初のパラメーターは ...(rest) パラメーターで作成された配列の一部でなくなっているので、出力は最初の値をスキップします。 function traceArgArray(x: int, ... args) { for (var i:uint = 0; i < args.length; i++) { trace(args[i]); } } traceArgArray(1, 2, 3); // output: // 2 // 3 オブジェクトとしての関数ActionScript 3.0 の関数はオブジェクトです。 関数を作成する場合、パラメーターとして別の関数に渡すことができるオブジェクトを作成するだけではなく、プロパティとメソッドも関連付けることができます。 パラメーターとして別の関数に渡される関数は、値渡しではなく、参照渡しによって渡されます。 関数をパラメーターとして渡す場合、識別子のみを使用し、メソッドを呼び出すために使用する括弧は使用しません。 例えば、次のコードは、addEventListener() メソッドへのパラメーターとして clickListener() という関数を渡します。 addEventListener(MouseEvent.CLICK, clickListener); ActionScript を初めて使用するプログラマーには奇妙に思えるかもしれませんが、他のオブジェクトと同様に関数にプロパティおよびメソッドを含めることができます。 実際には、どの関数にも、その関数用に定義されたパラメーターの数を格納する length という読み取り専用プロパティがあります。これは、関数に渡されたパラメーターの数を報告する arguments.length プロパティとは異なります。ActionScript では、関数に渡されたパラメーターの数がその関数用に定義されたパラメーターの数を上回ってもかまいません。 次の例では、strict モードでは渡されたパラメーターの数と定義されたパラメーターの数が完全に一致する必要があるので、standard モードでのみコンパイルされています。この例は 2 つのプロパティの違いを示します。 // Compiles only in standard mode function traceLength(x:uint, y:uint):void { trace("arguments received: " + arguments.length); trace("arguments expected: " + traceLength.length); } traceLength(3, 5, 7, 11); /* output: arguments received: 4 arguments expected: 2 */ standard モードで独自の関数プロパティを定義するには、関数本体の外側で定義します。関数プロパティは、関数に関連する変数の状態を保存できる準静的なプロパティになります。 例えば、特定の関数が呼び出される回数を追跡するとします。 ゲームを記述していて、ユーザーが特定のコマンドを使用する回数を追跡する場合に、こうした機能は便利ですが、この場合静的クラスプロパティを使用することもできます。 strict モードではダイナミックプロパティを関数に追加できないので、次の例は standard モードでのみコンパイルされます。この例では、関数宣言の外部に関数プロパティを作成し、関数が呼び出されるたびにプロパティをインクリメントします。 // Compiles only in standard mode var someFunction:Function = function ():void { someFunction.counter++; } someFunction.counter = 0; someFunction(); someFunction(); trace(someFunction.counter); // 2 関数のスコープ関数のスコープは、プログラム内の関数を呼び出すことができる場所だけではなく、関数がアクセスできる定義も決定します。変数識別子に適用される同じスコープの規則が関数識別子にも適用されます。 グローバルスコープで宣言された関数は、コード全体で使用することができます。 例えば、ActionScript 3.0 には、isNaN()、parseInt() などのコード内のどこでも使用できるグローバル関数があります。別の関数内で宣言された、ネストされた関数は、関数が宣言されている関数内のどの場所でも使用できます。 スコープチェーン関数が実行を開始するたびに、多数のオブジェクトおよびプロパティが作成されます。 最初に、アクティベーションオブジェクトという特別なオブジェクトが作成され、そこには関数本体で宣言されるパラメーターとローカル変数または関数が格納されます。アクティベーションオブジェクトは内部メカニズムであるため、そこに直接アクセスすることはできません。 次に、スコープチェーンが作成され、そこにはランタイムによって識別子宣言の有無がチェックされるオブジェクトが列挙されたリストが含まれます。実行されるどの関数にも、内部プロパティに格納されるスコープチェーンがあります。 ネストされた関数の場合は、スコープチェーンはそのアクティベーションオブジェクトから始まり、その後に親関数のアクティベーションオブジェクトが続きます。このように、スコープチェーンはグローバルオブジェクトに達するまで続きます。 グローバルオブジェクトは、ActionScript プログラムが起動すると作成され、すべてのグローバル変数および関数を含みます。 関数クロージャ関数クロージャは、関数の静的なスナップショットとそのレキシカル環境を含むオブジェクトです。関数のレキシカル環境には、関数のスコープチェーン内のすべての変数、プロパティ、メソッド、およびオブジェクトがその値と共に含まれます。関数クロージャは、オブジェクトまたはクラスとは別に、関数が実行されるたびに作成されます。 関数クロージャはその関数クロージャが定義されたスコープを保持することから、関数がパラメーターまたは戻り値として別のスコープに渡されると興味深い結果が生まれます。 例えば、次のコードは 2 つの関数を作成します。作成される関数の foo() は、矩形の面積を計算する rectArea() という名前のネストされた関数を返し、bar() は、foo() を呼び出し、myProduct という名前の変数に返された関数クロージャを格納します。bar() 関数が独自のローカル変数 x(値 2 を持つ)を定義する場合でも、関数クロージャ myProduct() が呼び出されると、関数 foo() で定義された変数 x(値 40 を持つ)を保持します。このため、bar() 関数は値 8 ではなく、値 160 を返します。 function foo():Function { var x:int = 40; function rectArea(y:int):int // function closure defined { return x * y } return rectArea; } function bar():void { var x:int = 2; var y:int = 4; var myProduct:Function = foo(); trace(myProduct(4)); // function closure called } bar(); // 160 メソッドは、そのメソッドが作成されたレキシカル環境に関する情報も維持するように動作します。 この特性が最も顕著なのは、メソッドがバインドメソッドを作成するそのインスタンスから抽出されるときです。 関数クロージャとバインドメソッドの主な違いは、バインドメソッドの this キーワードの値は常に最初に関連付けられたインスタンスを参照しますが、関数クロージャでは this キーワードの値が変化するという点です。 |
|