プログラムの一部分を意味のあるブロックとして名前を付けることができます。 特定の機能や意味を持たせる場合には、そのような名前付けをした方が良いでしょう。 この名前付けされたブロックのことを「関数(Function)」と 呼んでいます。関数を利用してプログラムを記述するには、関数の2つの側面を覚えておいて下さい。
- 関数の定義
- 定義された関数の呼出し
関数は、プログラム中で定義されますが、それだけでは実行されません。 必ず、その定義された関数を呼び出さないと実行がされないのです。
関数の定義の書式は、繰返しの書式と似ています。ブロックを使いますが、それに 名前をプログラムに導入するという書式と組み合わせたものと考えると良いでしょう。 以下のように記述します。
プログラムの一部を機能として分けながらプログラミングしていくのは、 プログラマにとってもわかりやすいですし、後でどこを変更すれば良いのかがわかりやすくなります。 関数の定義は次の書式に従って記述します。 これは、今までアプレットのstart関数で行なってきたものと同じです。
▼関数定義の書式
function 関数名( ) ブロック
関数名の後には、必ず丸括弧( )を記述します。 関数はこの丸括弧があることによって、変数と区別されています。 もしも、関数が呼出し時に受け取る情報がなければ、上のように丸括弧内には何も書かなくて構いません。
この上記の関数名の部分には、動詞や動詞句などにしても構いませんが、 関数の名前は小文字で始めるという不文律があります。 具体的な例を見てみましょう。 エッフェル塔を描く関数を定義しています。
// エッフェル塔を描く関数 function drawEiffel( ) { var canvas = document.getElementById("canvas"); var ctx = canvas.getContext('2d'); ctx.beginPath( ); ctx.moveTo( 40, 10 ); ctx.lineTo( 50, 10 ); ctx.quadraticCurveTo( 60, 80, 80, 110 ); ctx.lineTo( 60, 110 ); ctx.bezierCurveTo( 60, 90, 30, 90, 30, 110 ); ctx.lineTo( 10, 110 ); ctx.quadraticCurveTo( 30, 80, 40, 10 ); ctx.stroke( ); }
関数の定義のブロックの中に、更に繰返しのブロックがネストして入っていますので、 インデント(右への字下げ)が、繰返しの中ではだいぶ深くなっています。 名前をみてもわかるように「drawEiffel」というような動詞句(Phrasal Verb)にしても 構いません。動詞句にした場合は、動詞の部分を小文字で始め、複合語になっている名詞の部分を 大文字で始めます。
引数のない関数の呼出しは次の書式に従って記述します。
▼関数呼出しの書式
関数名( );
丸括弧の後に「;」(セミコロン)が必要なことに注意して下さい。これがJavaScriptでの文の終わりを示しています。 それでは、先ほど定義した「drawEiffel」を呼び出して使ってみましょう。
drawEiffel( );
プログラミングの世界では、これだけ記述するだけで動いてしまうので、摩訶不思議な 感じがします。丸括弧は、関数の呼出しを示しているので、書き忘れないでください。
JavaScriptのプログラムでは、関数の定義は、それを使用する前に記述します。 まずは実行される関数の定義から書いていった方がプログラム全体として何をするのかがわかりやすくなります。 最後の通常に実行されるプログラムが書かれます。 先ほどのdrawEiffel( )関数を保持したプログラムは、全体として次のように記述します。
// drawEiffel関数の定義 function drawEiffel( ) { var canvas = document.getElementById("canvas"); var ctx = canvas.getContext('2d'); ctx.beginPath( ); ctx.moveTo( 40, 10 ); ctx.lineTo( 50, 10 ); ctx.quadraticCurveTo( 60, 80, 80, 110 ); ctx.lineTo( 60, 110 ); ctx.bezierCurveTo( 60, 90, 30, 90, 30, 110 ); ctx.lineTo( 10, 110 ); ctx.quadraticCurveTo( 30, 80, 40, 10 ); ctx.stroke( ); } // プログラム全体の始まり drawEiffel( ); document.write( "Eiffel Tower<br>" );
先ほどの例について、関数の呼出しについて、 どのような実行の順序になっているか追いかけていきましょう。 まず、スクリプトが実行された後、drawEiffel( )という関数呼出しの文が実行されます。 そして、そこから注目する関数のdrawEiffelの定義にあるブロックの内部が呼び出されます。 ブロックの中の、getDocumentByIdなどの関数が順次呼び出され、実行が進んで行きます。 最後のstrokeを呼び出して、もうすることがなくなったら、関数呼出し側に戻ります。 そして、次のdocument.writeが呼び出されていきます。 関数は呼び出された場所の次の場所に戻るということを改めて確かめてください。
先の関数は、それぞれの関数の中でキャンバスのオブジェクトを作っていました。 ですから、関数が呼び出されるたびにキャンバスのオブジェクトが作成されていきます。 また、関数の終了と共に、キャンバスのオブジェクトへの参照がなくなってしまいます。 基本的に、変数は、宣言されたブロックの外側では生きることができないからです。 関数内のブロックで宣言された変数は、局所変数あるいはローカル変数(Local Variable) と呼ばれています。 そのため、プログラム全体で、定義された関数のすべてで 使用できる変数を宣言する必要があります。 それらの変数は、関数の定義の前に宣言しておく、あるいは初期化しておく必要があります。 通常のプログラミング言語では、このような名前の使用の 仕方を「大域宣言(Global Declaration)」あるいは「グローバル宣言」と呼んだりします。
// 関数の内部からも参照できる変数を宣言、初期化 var canvas = document.getElementById("canvas"); var ctx = canvas.getContext('2d'); // drawEiffel関数の定義 function drawEiffel( ) { ctx.beginPath( ); ctx.moveTo( 40, 10 ); ctx.lineTo( 50, 10 ); ctx.quadraticCurveTo( 60, 80, 80, 110 ); ctx.lineTo( 60, 110 ); ctx.bezierCurveTo( 60, 90, 30, 90, 30, 110 ); ctx.lineTo( 10, 110 ); ctx.quadraticCurveTo( 30, 80, 40, 10 ); ctx.stroke( ); } // プログラム全体の始まり ctx.translate( 200, 140 ); for ( var i=0; i < 36; i=i+1 ) { ctx.strokeStyle = "rgb(" + i * 6 + ", 0, 0 )"; drawEiffel( ); ctx.rotate( 10 * 3.141592 / 180 ); }
実行結果は以下のようになります。
熟練プログラマでもよくありがちなのは、関数を定義して、それで気持ちが落ち着いてしまって、 関数を呼び出す記述を入れるのを忘れることです。 関数を折角定義しても、呼び出されない限り実行されることはありません。
ローカル変数と同じ名前のインスタンス変数があった場合は、ローカル変数が優先されてしまいます。 これは、ローカル変数とインスタンス変数の型が異なっても同じです。 つまり、関数の中からはインスタンス変数が見えなくなってしまいます。 これを変数の隠蔽(Shadowing)と呼んでします。
エッフェル塔を描くときなど、開始座標や大きさを関数に直接渡せると便利です。 関数を呼び出すときに、何らかの情報あるいはデータを一緒に渡せるという機能が、 ほぼすべてのプログラミング言語に備わっています。 この関数呼出しのときに一緒に渡されるデータのことを「パラメータ(Parameter)」 あるいは「引数(Arguments)」と呼ばれます。 このとき、パラメータあるいは引数には2つの側面があります。 呼出し側のプログラムでは、パラメータに実際の値(定数値)を入れて関数に渡します。 このときのパラメータ、つまり呼び出されたときの値のことを「実パラメータ」あるいは「実引数」と 呼びます。 一方、呼び出された関数の方では、それを何らかの形で受け取らなければなりません。 ほとんどのプログラミング言語では、変数という形で受け取ります。 この実パラメータを受け取るための変数のことを、「仮引数」と呼んでいます。
引数のある関数を定義するときは、 関数の名前にある丸括弧の中に引数(あるいはパラメータとも呼ばれています)を 受け取る変数の名前を記述します。 関数の定義の書式と呼出しは、それぞれ以下のようになります。[A]…は、Aの0回以上の繰返しと思って下さい。
▼引数のある関数定義の書式
function 関数名( 仮引数の変数名 [, 仮引数の変数名 ]… ) ブロック
仮引数の名前は、関数の中でだけ使える変数名と思って下さい。 たとえば、次の関数「drawEiffel」では、開始点のx座標、y座標を受け取る仮引数として、 x, yという変数を用いています。
function drawEiffel( x, y ) { ctx.beginPath( ); ctx.moveTo( x+40, y+10 ); ctx.lineTo( x+50, y+10 ); ctx.quadraticCurveTo( x+60, y+80, x+80, y+110 ); ctx.lineTo( x+60, y+110 ); ctx.bezierCurveTo( x+60, y+90, x+30, y+90, x+30, y+110 ); ctx.lineTo( x+10, y+110 ); ctx.quadraticCurveTo( x+30, y+80, x+40, y+10 ); ctx.stroke( ); }
もちろん、複数の仮引数を指定することも可能です。次の関数「drawEiffel」は、 塗り潰すかどうかを表す「filler」の引数も受け取っています。
function drawEiffel( x, y, filler ) { ctx.beginPath( ); ctx.moveTo( x+40, y+10 ); ctx.lineTo( x+50, y+10 ); ctx.quadraticCurveTo( x+60, y+80, x+80, y+110 ); ctx.lineTo( x+60, y+110 ); ctx.bezierCurveTo( x+60, y+90, x+30, y+90, x+30, y+110 ); ctx.lineTo( x+10, y+110 ); ctx.quadraticCurveTo( x+30, y+80, x+40, y+10 ); if ( filler == true ) { ctx.fill( ); } else { ctx.stroke( ); } }
ここではJavaScriptでの名前識別方法であるシグネチャ(Signature)と呼ばれる規則について説明します。 JavaScriptでは関数の識別には、名前だけでなくて、仮引数の個数なども見ています。 ですから、JavaScriptでは、同じ名前でも違うパラメータの型を持っている関数は別物とみなします。 これを名前の多重定義(Overloading)と呼んでいます。 なお、関数を識別するために用いられる関数の特徴のことをシグネチャ(Signature)と呼んで、 名前だけの識別と区別しています。これは、次のような要素で構成されています。
▼シグネチャを決める要素
・関数の名前
・仮引数の個数
同じ名前を持っていても、シグネチャが違っていれば、関数は別のものだと認識されます。 しかし、混乱を避けるために、なるべく既存の名前と衝突しないように気をつけた方が無難でしょう。 それはプログラムの読みやすさを維持するためです。
今度は定義した引数のある関数を呼び出して使ってみましょう。 呼び出して使わない限り、定義した関数が使われることはありません。 呼出しの書式は次のようになります。
▼引数のある関数呼出しの書式
関数名( 実引数の式 {, 実引数の式 }… );
整数の引数が2つ必要な「drawEiffel」の場合、どのように呼び出すのか記述してみましょう。
drawEiffel( 140, 140 ); // 140, 140の地点からエッフェル塔を描く
もし、仮引数が複数あれば、順番を入れ替えずに実引数の個数分だけ式を記述して、関数を 呼び出します。先ほどの「drawEiffel」を呼び出して使ってみましょう。
drawEiffel( 140, 140, true ); // 140, 140の地点から塗り潰しでエッフェル塔を描く
3番目に定義された、3つの引数を必要とするdrawEiffel関数を使って、横にいくつか 描画してみましょう。
for ( var i=0; i < 10; i=i+1 ) { ctx.fillStyle = "rgb( 0, 128,"+i*25+" )"; drawEiffel( i * 100, 0, true ); }
これを実行すると以下のようになります。
他の例として、簡単すぎて、あまり意味はないのですが、引数として与えられた文字列にちょっとした前置きをつけて、 文字端末に表示する関数を定義してみましょう。
function drawMessage( String message ) { document.write( "受け取った文字列: " + message + "<br>"); }
この関数では、文字列を引数として受け取り、それに仮にmessageという名前をつけています。 これは、変数として使うことができます。 この関数では、この変数を更に別の関数document.writeを呼び出すときのパラメータとして用いています。 この関数を利用してみましょう。
function drawMessage( String message ) { document.write( "受け取った文字列: " + message + "<br>"); } drawMessage( "I am called by Application." ); drawMessage( "Do you understand on calling method?" );
文字端末へ出力できるような実行環境で実行すると、描画要求がされる度に、 start関数からdrawMessageへ呼出しが行なわれ、次のように2行のメッセージが文字端末に表示されることになります。
受け取った文字列: I am called by Application. 受け取った文字列: Do you understand on calling method?
復習になりますが、ここでもう一度、引数について整理してみましょう。 引数を必要とする関数は、呼出し側で指定した引数を評価し、必ず最終的に定数に置き換えてから、 呼び出される関数に渡されます。 すなわち、引数の評価を先に行なってから、関数呼出しが行なわれるのです。 それぞれの側での引数を区別するために、次のように呼び分けています。
実引数(じつひきすう:Actual Argument) 呼出し側で指定した式や定数
仮引数(かりひきすう:Formal Argument) 関数側で受け取るために指定した変数
引数は、パラメータは(Parameter)とも呼ばれることがありますので、仮引数のことは、 仮パラメータ(Formal Parameter)と呼ぶことがあります。 同じように実引数も、実パラメータ(Actual Parameter)と呼ばれることがあります。
実引数が先に評価されて関数が呼び出される例を見てみましょう。 実引数として(計算)式を利用してみます。 先ほどのdrawMessageを呼び出す際に、次のように記述されたとします。
var bcount = 12; drawMessage( "日本の戦艦は" + bcount + "隻である" );
この場合には、括弧内の実引数の式がまず評価されます。 文字列の結合が行なわれ、評価結果として、"日本の戦艦は12隻である"という文字列定数が生成されます。 呼び出されたdrawMessage関数の実行では、この文字列定数が仮引数の変数に代入されますので、 最初に次のような代入が行なわれたの同じに状態で実行が開始されます。
message = "日本の戦艦は12隻である";
ちなみに、ここでカウントされている12隻の戦艦とは第2次世界大戦の頃の、 大和・武蔵・長門・陸奥・伊勢・日向・金剛・榛名・山城・扶桑・比叡・霧島を指します。
大きい四角形から、小さい四角形まで中心を揃えて描くプログラムを作りなさい。 引数のある関数を定義します。 関数としては、一辺のサイズ受け取って四角形を描くdrawSquareを定義します。 各々の四角形の間には適当な間隔が空いているものとします。 ファイル名はPyramidNest.htmlとし、実行してみなさい。
drawMessage関数は、動作原理を説明するために用いましたが、もう少し実用的な関数を記述してみましょう。 たとえば、三角形を書くような関数を定義しておいて、これを呼び出せるようにしてみましょう。 この関数に与えるべき情報は、次のようなものが考えられます。
・上部の頂点の相対的なx座標、y座標
・1辺の長さ
これだけの情報を引数として渡せば、三角形が描けます。次のように定義します。
function drawTriangle( x, y, len ) { ctx.beginPath( ); ctx.moveTo( x, y ); ctx.lineTo( x + len / 2, y + len * 1.7320508 / 2 ); ctx.lineTo( x - len / 2, y + len * 1.7320508 / 2 ); ctx.lineTo( x, y ); ctx.stroke( ); }
この関数を呼び出して使ってみましょう。 まずは、定数値で1つだけ三角形描くように使ってみます。
drawTriangle( 100, 100, 50 );
さらに、繰返しで実引数の値を変えながら、呼出しを行なってみましょう。
for ( var i=10; i <100; i += 10 ) { drawTriangle( 100, 100+i, 200-i*1.7320508 ); }
こんな感じの図になります。
ローカル変数のときと同様に、インスタンス変数と仮引数が同じ名前だったらどうなるでしょうか? 次のプログラムは、2つの関数を持っています。 start関数からdummy関数を呼び出しているのですが、dummy関数では変数のxに10を代入しています。 変数xは、インスタンス変数(整数で最初に何も代入されていない場合は0が代入されています)と dummy関数の仮引数と両方に宣言されています。 さて、document.writeでは一体どのような値が文字端末に表示されるのでしょうか?
var x ; // グローバル変数として宣言されたx function hello( ) { x = 30; // グローバル変数に30を代入した。 dummy( 20 ); drawMessage( x ); // このxは、グローバル変数の方を指す } function dummy( x ) { // 関数の仮引数として宣言されたx x = 10; // このxは、仮引数変数の方を指す }
結局、ローカル変数名で起こったことと同じことが起こります。 グローバル変数と同じ名前で仮引数の変数やローカル変数が宣言されている場合は、 仮引数あるいはローカル変数の方が優先されます。 この例では、dummy関数の中での代入は、仮引数の変数に行なわれています。 グローバル変数には何も代入されませんので、文字端末には、元々の値の30が表示されることになります。 結局、仮引数変数のxがグローバル変数の方のxを見せなくしていました。 これも変数の隠蔽(Shadowing)と読んでいます。
※注1 仮引数の変数は、普通の変数として扱えますので、仮引数にも値を代入できてしまいます。 この場合は、元々保持していた値、すなわち、呼出し側から渡された実引数は書き換えられてしまいます。
1つのクラスの定義の中で、関数名と変数名(インスタンス変数やローカル変数)が同じ名前を持っていたとしてもエラーが出ません。関数はシグネチャで識別されているからです。 たとえば、alert関数がありますが、 alertという名前のローカル変数やインスタンス変数を宣言しても構いません。 しかし、このような関数と同じ名前を持つ変数がプログラム中に存在すると、かなり紛らわしいので、 なるべく避けた方が無難でしょう。
関数名をdrawArcとしましょう。中心のx, y座標と半径(r)と角度差(angle)の4つを仮引数とします。 角度差は、東の方向を0度として反時計方向にどれくらいの角度を描くのかを決めます。
// 円弧を描く関数を記述します function drawArc( x, y, r, angle ) { ctx.save( ); ctx.scale( 1, -1 ); // 上下反転させる ctx.beginPath( ); var theta = angle * Math.PI / 180; if ( theta >= 0 ) { ctx.arc( 0, 0, r, 0, theta, false ); } else { ctx.arc( 0, 0, r, 0, Math.PI*2+theta, true ); } ctx.stroke( ); ctx.restore( ); } // 使ってみます drawArc( 100, 160, 150, 120 ); drawArc( 150, 160, 100, -180 ); drawArc( 130, 160, 80, 180 ); drawArc( 150, 160, 60, -180 );
自作の関数を複数定義した場合は、1つの関数から別の呼び出すことができます。たとえば、先ほどの三角形を描く関数を利用して、更に家を描くような関数を定義してみましょう。二等辺三角形が家の屋根で、その下に家の壁作ってみましょう。渡す引数としては、家の屋根の頂点のx座標とy座標、家のサイズをの4つです。これらの情報をそのまま用いて、drawTriangleを呼び出してやります。そうすると、屋根の三角形が描かれますので、その下にstrokeRectを用いて、壁を描画します。家のサイズは、壁の1辺のサイズになっています。
function drawHouse( x, y, size ) { drawTriangle( x, y, size ); ctx.strokeRect( x-size*3/8, y+size*1.7320508/2, size*3/4, size*3/4 ); }この関数を利用して、使ってみます。
for ( var x = 50; x < 800; x += 100 ) { drawHouse( x, 50, 70 ); }
Javaでは、関数の判別を、シグネチャで判別していますので、関数の名前だけでなく、 パラメータの型や数についても解析しています。 ですから異なる型のパラメータを受け取る「同じ名前の関数を多重に定義」できます。 これをJavaでは関数名のオーバーロード(Overload)と呼んでいます。 また、プログラミング言語一般的には、そのような関数を総称して、 多相型(Polymorphic)の関数とも呼ばれています。
葉っぱを描く関数を以下のように定義して、葉っぱを 描いてみなさい。drawLeaf関数を記述しています。
// 普通の太さの葉っぱ function drawLeaf( x, y, size ) { ctx.save( ); ctx.translate( x, y ); ctx.beginPath( ); ctx.moveTo( 0, -size ); var half = size/2; var width = size*3/4; ctx.bezierCurveTo( width, -half, width, half, 0, size ); ctx.bezierCurveTo( -width, half, -width, -half, 0, -size ); ctx.stroke( ); ctx.restore( ); } // 異なる細さを指定する葉っぱ // scale…0.25(非常に細い) 0.5(細い) 0.75(普通) 1.0(太め) // function drawLeaf( x, y, size, scale ) { ctx.save( ); ctx.translate( x, y ); ctx.beginPath( ); ctx.moveTo( 0, -size ); var half = size/2; var width = size * scale; ctx.bezierCurveTo( width, -half, width, half, 0, size ); ctx.bezierCurveTo( -width, half, -width, -half, 0, -size ); ctx.stroke( ); ctx.restore( ); }
上記の「葉っぱを描く」関数を利用して、次のような蝶を描きなさい。 DrawButterfly.htmlというファイル名で。
上記の「葉っぱを描く」関数を利用して、次のような花を描きなさい。 DrawFlower.htmlというファイル名で。
蝶を描く関数を大きさを変更できるように定義して、蝶を横に並べて 描いてみなさい。これを複数の段で描いていきますが、下の段に行くほど、蝶の大きさが大きくなるようにします。
引数は、呼出し側から関数への引数の受渡しでした。 関数の実行の終了時には、関数側から呼出し側へ、何らかの情報を返すことができます。
関数の終了は、関数を定義しているブロックの最後まで実行が終わってしまえば、自動的に処理が呼出し側に戻ります。 しかし、明示的に関数を終了したい場合は、return文を用います。 関数をどの時点でも強制終了させて、呼出し側に制御を戻せます。 加えて、値を返す関数を作成したい場合は、呼出し側に値を返すときに、このreturn文の後に式を記述します。 次の書式のように、returnの後に式を記述することができます。
▼return文で値を返すときの書式
return 式 ;
終了する前に、このreturn文の後に続く式が評価されます。 評価が終わって一定の値になりましたら、呼出し側にその値が戻されます。 ここで返される値のことを戻り値(Return Value)あるいは返り値と呼んでいます。 それでは、戻り値を返すような簡単な関数を定義してみましょう。
function square( x ) { var y = x * x; return y; }
このsquareという関数は引数として受け取った値の2乗を計算して、計算結果を戻り値として返します。 わかりやすくするために、ローカル変数yを用意して、それに計算結果を保持させるように記述しています。 returnの後は、式を書くことができますので、この関数は直接的に次のように定義することもできます。
function square( x ) { return x * x; }
もう1つの例を見てみましょう。 2つの引数を受け取り、大きい方の数を返す関数を定義してみます。
function greater( x, y ) { if ( x > y ) { return x ; } else { return y; } }
戻り値は1つしか記述できないのでしょうか? C言語系統のプログラミング言語は、関数は元々は関数的な取り扱いでしたので、 戻り値は1つだけしか記述できません。もし、複数の値を戻したいのであれば、 配列にしたり、オブジェクトの形に直す必要があります。 いくつかのスクリプト言語(Luaなど)は、複数の値を戻す書式が用意されています。
▼戻り値のある関数の定義
function 関数名( 仮引数 ){
:
return 戻す値を示す式;
}
戻り値のある関数を呼び出して使ってみましょう。 次のように、代入文の式の中に関数呼出しを記述します。実引数の個数は、定義された仮引数の個数と同じ分だけ用意します。
▼戻り値のある関数の呼出しもちろん、代入文など用意しないで、戻り値を受け取らなくても構いません。あるいは、関数の呼出しを複雑な式の中に埋没させても構いません。さて、それでは、先ほど定義した関数を、この書式に基づいて、呼び出して使うための記述をしてみましょう。変数を宣言しながら、代入を行なっています。
変数 = 関数呼出し( 実引数 );
var z = square( 30 ); // zには900が代入される var y = square( z + 20 ); // yには846400が代入される var w = greater( z, y ); // wにも846400が代入されるこのような呼出しを行なった場合、実際には、どのような実行の順番になっているのでしょうか?簡単な例を用いて、少し、実行の様子を追ってみましょう。 関数の呼出しの記述:
var result = square( 45 * 10 ); →45*10という式が評価されて、定数値450になり実引数としてsquare関数に渡されます。関数の定義:
var square( x ) { return x * x ; } →squareが呼び出され、仮引数変数xに、実引数の定数値450が代入されます。 →x * xと書かれた式の計算結果の定数値202500が戻り値として呼出し側に返送されます関数の呼出し後の代入:
var result = square( 45 * 10 ); →square( 45 * 10 )の部分が、戻り値の定数値202500に置き替わります。 →変数resultを宣言し、result = 202500;という代入文が実行されます。このように、実行の制御が一度定義された関数に移り、計算が行なわれた後、再び呼出し側に戻ってきました。呼出し側では、関数を呼び出した部分の記述が、戻り値に置き換えられます。
document.write( "result:" + square( greater( 30, 40 ) - 10 ) ) );実際に、この1文がどのように実行されるかを追ってみましょう。
1. greater( 30, 40 )が実行される→評価結果は40になる
2. square( 40 - 10 )が実行される→評価結果は900になる
3. document.write( "result: " + 900 );が実行される
関数の名前は、小文字から始まり、動詞が使われるという不文律がありました。また、動詞+名詞でも構わず、その場合は名詞の部分は大文字から始まりました。更に戻り値のある関数では、使われる動詞にも一般的な傾向があります。情報を返す関数では、getという動詞が用いられます。また、論理値を返す関数では、isという動詞が多く名前の中に使われています。良く用いられる関数の名前を少しだけ列挙してみました。
情報を返す: getColor, getFont, getName, getKey, getX, getY, getImage
論理値を返す: isVisible, isTurtle, isMouseDown, isKeyDown, equals
自分で定義する際にも、関数に名前をつけるときにこのような慣習を守っておくと、後でプログラムを読み返したときにわかりやすいでしょう。
戻り値を返す関数の中でも、論理値を返す関数の場合は、if文やwhile文などの条件を記述する式に用いられます。たとえば、現在の時刻がお昼の12時から14時の間であることを判別するための関数を記述してみます。プログラムの初心者ならば次のようにプログラムするでしょう。論理値を返すので、名前は、慣習に従って、動詞のisをつけています。現在の時刻を24時間として時を変数hourに求めています。
funciton isLunchTime( ) { var hour = (new Date( ) ).getHour( ); if ( hour >= 12 && hour < 14 ) { return true ; } else { return false; } }
ところが、少し慣れてくると、論理型の変数に条件式が直接代入できることがわかります。 条件式は、trueかfalseに評価されますので、それを変数に代入して、その値を返してあげれば良いわけです。
function isLunchTime( ) { var hour = (new Date( ) ).getHour( ); var condition = ( hour >= 12 && hour < 14 ); return condition; }
最後には、論理型の変数も使わずに、直接return文の戻り値を返す式のところに条件式を記述するようになります。
function isLunchTime( ) { var hour = (new Date( ) ).getHour( ); return hour >= 12 && hour < 14; }
return文の戻り値の式は、条件式になっています。このように記述すると、条件式が評価されて、論理値のtrueあるいはfalseが返されることになります。今度は、この関数をstart関数などの他の関数の中から呼び出して記述するような場合を考えてみましょう。
if ( isLunchTime( ) == true ) { document.write( "It is lunch time! Shall we have lunch?" ); }
ところが、呼出し側でもこのように論理値と等しいかどうか比較しなくても、論理値そのものを返してくるのですから、if文の括弧の中に 関数の呼出しだけを直接記述することができます。比較結果は、どうせtrueかfalseという論理値に評価されることを 思い出して下さい。
if ( isLunchTime( ) ) { document.write( "It is lunch time! Shall we have lunch?" ); }このように論理値を戻り値として返す関数を用いる場合は、関数の呼出しを直接if文の条件式の中に記述して使います。
大きい方の数を返す関数、小さい方の数を返す関数、2つの値の平均値を返す関数を それぞれ作成し、ユーザの入力に応じて表示させるようにしなさい。ファイル名はPractice0811.htmlとします。
べき乗を計算する関数を作りなさい。パラメータの入力は、 基となる数と指数の2つで、いずれも整数型です。 戻り値は、基本的には整数型ですが、結構大きな数になる場合は、実数になることもあります。 ユーザの入力に応じて結果を表示させるようにしなさい。ファイル名はPractice0812.htmlとします。
パラメータで与えられた数(整数)が素数(その数と1以外では割り切れない数)か どうかを計算する関数を作りなさい。 戻り値は、論理値になります。素数だとtrue、素数でないとfalseを返します。 この関数を繰返しの中で呼び出す形で、3から99までの 奇数について、素数かどうかを表示するプログラムを作りなさい。 ファイル名はPrimeDisplay.htmlとします。
2つのパラメータで与えられた数(1つ目が割られる数、2つ目が割る数)の整数除算の結果を返します。 ファイル名はIntegerDivide.htmlとします。
3つのパラメータで与えられた数について、最初の引数が対象となる値です。 この値が、2番目に与えられる下限値よりも小さいときは、下限値を返します。 この値が、3番目に与えられる上限値よりも大きいときは、上限値を返します。 下限値と上限値の間に入っていれば、そのまま最初の引数の値を返します。 ファイル名はInRange.htmlとします。
再帰呼出し(Recursive Call)とは、ある関数から、同じ関数を直接(あるいは間接的に)呼び出すことを指します。 この場合、呼び出される関数は再帰呼出しに対応していないといけません。 多くの場合は、仮引数を必要とし、その引数の値に応じて、一般的に、次のように処理を分けて考えます。 これは、数学的帰納法と同じような形だと思って下さい。
基底レベル…それ以上は、再帰的に呼び出されないレベル、このレベルの処理を考える
それよりも大きいレベル…再帰的な呼出しがどこかに入る、前後の処理を考える
たとえば、総和を求める場合について考えます。
// 再帰関数の定義 function summetion( n ) { if ( n <= 1 ) { // 基底レベル(nが1以下だったら) return 1; } else { // それよりも大きいレベル return n + summetion( n-1 ); // 引数を1つ小さくして再帰呼出し } } var sum; // 総和を求める変数 sum = summetion( 10 ); // n を10で呼出し開始
これは、再帰呼出しを使わなくても、以下のように繰返しで記述しても同じ結果が得られます。
function summetion( n ) { var sum = 0; for ( var i=1; i < n; i += 1 ) { sum = sum + i; } return sum; }
すなわち、ある意味で繰返しと再帰呼出しは同じものと考えることができます。 また、以下のような定理が存在していますが、再帰で考えるのが面倒な場合は、繰返しで実現することが多いと 思われます。
同じように、階乗(n! = 1 × 2 × .... × n)も再帰関数を使って定義することができます。 数学的な記述の仕方をすると、次のようになるでしょうか。
JavaScriptで記述すると以下のようになります。
// 階乗の再帰を使った定義 function factorial( n ) { if ( n <= 1 ) { // 基底レベル(nが1以下だったら) return 1; } else { // 1よりも大きいレベル return n * factorial( n-1 ); // 引数を1つ小さくして再帰呼出し } } var prod // 階乗を求める変数 prod = factorial( 10 ); // n を10で呼出し開始
総和を求めるときと比べて、1よりも大きいときのレベルの演算が+から*に替わっているだけになっています。
関数とは処理の固まりを定義するものです。既にイベントモデルから呼び出す処理の固まりとして関数を使用しました。ここでは、この関数についてもう少し詳しくみていきましょう。
まずは、関数の定義方法です。JavaScriptでは幾つかの関数の定義の方法があるのですが、ここでは一番基本的な関数の定義について説明します。下記をみてください。これが関数を定義する際の基本的な書き方になります。関数を定義する場合には、はじめに「function」と書きます。その後にくるのが関数名です。関数名には使える文字に変数と同様の制限があります。先頭の文字は英字など数字以外でなければなりませんし、大文字小文字の区別もあります。関数名も変数名と同様に、できるだけ関数の中身が関数名をわかるようなものを選択しましょう。多くのプログラムでは、getData()のように、動詞と名詞をこの順に組み合わせて関数名を定義することが多いようです。関数名の後には括弧がきます。括弧の中には仮引数が入ります。仮引数とは、関数が呼び出される際に呼び出す側から渡される値を受けるための変数です。関数の中では、ローカル変数として扱われます。仮引数は幾つあっても構いません。なくとも構いません。実際、前回までにみてきた関数では、引数はありませんでした。また、関数には返り値というものを設定する事ができます。返り値は、関数を呼び出した部分に対して、値を返すものです。
function func(variable1, variable2, ...) { /* * 関数の中で実行したい処理。 * この部分ではvariable1、variable2などの変数を使う事ができる。 */ return retval; // 省略可能 }
関数、仮引数、返り値の概念を下記の図に示します。この例では、add()という関数を定義して使っています。add()という関数は、仮引数としてxとyの二つを持っています。関数の中では、この二つの変数を足し算をして別の変数zに格納し、そのzをreturn文を使ってもとのプログラムに返しています。呼び出しもとのプログラムの方をみてみましょう。aという変数を定義して、それを2で初期化しています。その次の行では、変数sumを定義しています。次の代入文のところでadd()という関数が出てきます。add()関数を呼び出すにあたって、引数に1という値とaという変数が指定されています。関数を呼び出すと制御が一旦関数側に移るのですが、この際、引数に指定された値が関数の仮引数にセットされます。この例の場合は、xに数値である1が、yにはaの中の値である2がセットされます。このため、add()関数の中のz=x+yが実行されるときには、xが1、yが2になっており、zは3になります。その後、add()関数の中の最後の行であるreturn文によって、関数が最終的に呼び出し側のプログラムに返す値がzであることが示されています。このため、呼び出し側ではadd(1, a)の評価結果は3となり、結果として変数sumに3が代入されます。
実際に上のプログラムを動かしてみましょう。次のようなプログラムを書いてみてください。「sum = 3」と表示されるはずです。
<script type="text/javascript"> // add()関数の定義 function add(x, y) { var z = x + y; return z; } // 呼び出し側のプログラム var a = 2; var sum; sum = add(1, a); document.write("sum = " + sum); </script>
下記は、実行結果です。
上記で変数宣言を行わない場合は問題を起こす場合が多いと述べましたが、なにが問題なのでしょうか? 実は、多くのプログラミング言語には「スコープ」と呼ばれる概念があります。これは、名前空間をある場所に区切って使えるようにしましょう、というものです。たとえば、dateやsumといった一般的な名前の変数は、全てのプログラマの間で名前が重ならないようにすることは難しく、多くのプログラムやプログラムの部分で使われる可能性があります。このような場合にも名前が重なってプログラムが変な動きをしないように、名前が有効になる範囲をプログラムの一部分に限ってしまおうという考え方です。
次のプログラムを見てください。
<script type="text/javascript"> var word = "こんにちは"; // hello関数1 function hello1() { var word = "こんばんは"; window.alert(word); } // hello関数2 function hello2() { window.alert(word); } // hello関数3 function hello3() { word= "こんばんは"; window.alert(word); } // hello関数4 function hello4() { { var word = "こんばんは"; } window.alert(word); } </script> <input type="button" value="hello1" onclick="hello1()"> <input type="button" value="hello2" onclick="hello2()"> <input type="button" value="hello3" onclick="hello3()"> <input type="button" value="hello4" onclick="hello4()">
実際の動きは下記のとおりです。ボタンを押してみましょう。何が起きているかわかりますか? ボタンを押す順序によっても動きが変わりますので、ブラウザのリロード機能などを駆使して動きを把握してみてください。デバッガを使うのもよいとおもいます。
hello1ボタンを押すと、「こんばんは」と表示されましたね。これは、8行目のwindow.alert()で表示されています。表示内容は、すぐ上の7行目において宣言されている変数wordの値です。
初めに、hello2ボタンを押すと、「こんにちは」と表示されます。これは、13行目の文でアラートを出しています。値としては、3行目において初期化したものです。ここで、7行目の代入文で代入された値が表示されていないことに注意してください。7行目の代入文が無関係なのはなぜでしょうか? 授業の第二回の時に「プログラムは基本的に上から下へ実行されます」と言いました。この規則に則ると、13行目のwindow.alert()より前に7行目の代入文があるので、wordの値は「こんばんは」になるはずです。しかし、6行目から9行目までは関数宣言です。関数宣言については前回簡単に説明しましたね。この部分は宣言であって、実際に他から呼び出されるまでは実行されることはありません。そのため、変数wordの値は変更されることはありません。しかし、この説明だと、一度hello1ボタンを押したあとでhello2ボタンを押すと、「こんばんは」と表示されるはずです。しかし実際には、たとえhello1ボタンを押した後にhello2ボタンを押しても「こんにちは」と表示されます。実は、このプログラムでは、3行目で宣言されているword変数と7行目で宣言されているword変数は別物なのです。これがスコープです。
JavaScriptにはグローバルスコープとローカルスコープと呼ばれる2種類のスコープが存在します。スコープというのは変数や関数を参照できる範囲を決めているもので、グローバルスコープの場合にはプログラムのどこからでも参照が可能です。一方で、ローカルスコープの場合は、関数の中に閉じており、関数の中だけでしか参照することができません。上の例では、3行目のword変数はグローバル変数(グローバルスコープの変数)として、7行目のword変数はローカル変数(hello2関数の中からのみ参照可能な変数)として定義されています。このため、hello2関数の中からは3行目で宣言したword変数にはアクセスできるのですが、hello1の中のword変数にはアクセスできません。逆にみると、hello1の中からword変数を参照するとローカル変数の方が参照されて、グローバル変数にはアクセスできなくなってしまっているのです。
ローカル変数の宣言は、関数のなかで var を使って宣言を行うことによって可能です。関数外で宣言をした場合には、3行目の宣言のようにグローバル変数となります。ややこしいのは、関数の中で var による宣言をしなかった場合には、その変数はグローバル変数として扱われるということです。
グローバル変数を使う場合は細心の注意が必要です。たとえば、上記の例においてhello3ボタンを押した後に、hello2ボタンを押してみてください。「こんばんは」と表示されましたね。これは、hello3()関数の中でグローバル変数であるword変数の中身を書き換えてしまったため、hello2()関数にも影響を与えているのです。
これまでC言語などの別の言語を学んだことがある人が陥りがちな罠が、hello4ボタンです。JavaScriptには、グローバルスコープと関数局所スコープしかありません。このため、hello4()関数のように新しいブロックを作っても、関数内のローカル変数となりますので注意してください。初めてプログラミングを学ぶ方は、このように新しいブロックを作ることで別のスコープを明示的に作ることができるプログラミング言語もあるのだと思っていただければ結構です。