演習9 - 関数の作成

9.0 はじめに

 これまでの演習でも、幾度となく「関数」は現れてきました。線を引くlineTo(x,y)や、0から1までのランダムな小数値を返すMath.random()などが、その例です。「関数」の肝は、ただ一つの関数式を書くだけで、関数の内部にある複数の処理を記述したことと同じ効果を持つ点にあります。簡単に言うと、関数は「処理の外注」です。複雑な仕事を引き受けてくれるだけでなく(lineTo(x,y)は現在のパスの位置から線を結び、さらにパスの位置を更新します)、仕事の成果(計算結果)を返してくれることもあります(Math.random()はランダムな数字を生成して返してくれます)。lineToやrandomはシステムからあらかじめ用意されている関数でしたが、今回の演習では、自分のためのオリジナルな関数をつくる練習をしていきましょう。関数を自由に自作できるようになると、コードを非常にシンプルに見せることができるようになるとともに、複雑な処理を効率的に記述することができるようになります。以下では、「直線で円を描く処理」に関するプログラムの学習を通して、関数の使用方法についての理解を深めていきます。

9.1 実は、これまでも自作していた関数

HTMLの構造の復習

これまでの演習で、HTMLファイルの構造は以下のようになっていました。

演習では、基本的にはdraw(){ ... }の中身を変えて、様々な処理を実現していましたね。 このとき、すでにあなたは、「draw」という名前の引数なしの関数を定義する作業をしていたことになります。 この関数は、<body>タグの「onLoad属性」に関連づけられることで、 HTMLファイルの本文が読み込まれたときに一回きり実行するように指定されています。

関数は、以下のようにすると、いくらでも増やすことができます。

上の例は、HTML本文が読み込まれたときに、draw1()、draw2()、draw3()の関数を順に実行することを指示しています。

あるいは、次のように書くこともできるでしょう。

この例では、scriptタグの中で、draw()関数とdrawA()関数が定義され、draw()関数の中でdrawA()関数が呼び出されています。 以下の演習では、この後者の例を実装していくことになります。

javascriptファイルとHTMLファイルの分離

さて、このように、スクリプトファイルの中にいくつもの関数を定義していくことを想定していくと、 HTMLファイルの中でscriptタグの記述だけがどんどん肥大化していってしまい、 HTMLファイル全体の構造が極めてわかりずらくなります。 従って、今後は、javascriptファイルとHTMLファイルを分離するようにしましょう。

ここではHTML本体を「work.html」、javascriptファイルの名前を「script.js」としましょう。 この2つのファイルが同じ階層にあるとします。

  • work.htmlの中身 [#f283869d]
    ↑)7行目でscriptファイルの属性として、参照するファイルのtypeとしてjavascriptファイルを、 ファイル名として「script.js」を指定しています。 javascriptを記述するファイルの拡張子は「js」とします。
  • script.jsの中身 [#w85f2edd]
    ↑)ファイルを分離するからといって何も新しいことを考える必要はありません。 これまで<script></script>の中に書いていたものを、jsファイルの中にそのままコピーすれば良いのです。 一点だけ補足しておくと、上のファイルでは、これまでdraw関数の内部で宣言されていたキャンバスオブジェクトと描画オブジェクトのための変数が、 関数の外部で宣言されています(2行目と3行目)。このように宣言された変数をグローバル変数と呼び、 関数の内部で宣言されたローカル変数とは区別されます。 ローカル変数は関数の中でしか呼び出すことができませんが、グローバル変数は、同一ファイルの中にあるあらゆる関数の中からアクセスすることができます。 今回の演習では、複数の関数を作成し、それぞれから同一の描画オブジェクトに対して描画の命令を行うことを想定していますので、 あらかじめ、キャンバスオブジェクトと描画オブジェクトはグローバル変数として関数の外で宣言してしまいましょう(同様にキャンバスの幅と高さもグローバル変数として、どの関数からも再利用可能にしてしまうと、後々の作業がとても楽です)。
  • ダウンロード [#j81a9a97]
    • 以上の2つのファイル(work.html, script.js)の入ったフォルダを圧縮したファイルを以下に置いておきます。
    • ダウンロード

9.2 ランダムな線の集積によって円を描く(関数の利用)

コンセプト

円を描くにはarc関数を使うのが常套手段ですが、 ここでは、もう少し「味のある円」をつくってみることにしましょう。 線をぐちゃぐちゃランダムに動かしつつ円の内部をなぞっていく感じです

必要となる主な材料は以下の通りです(変数名は次のコードの中の記述に合わせています)。

  • 円の中心:(x0,y0)
  • 円の半径:r
  • 一回に引く線分の最大の長さ:lmax
    • rより小さく必要があります。
  • 線の本数:lsum
    • 一定数以上無いと円の形に見えませんが、ありすぎても、単に黒く塗りつぶした円との違いがなくなります。

コード

- - - - - 実行結果 - - - - - - - - - - - - - - - - - - - -

(sample9A/work.html)

Drawing

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

解説

  • (33-35行目)
    • 新しく線を結ぶ移動先の候補 (xnew, ynew) をランダム関数を使って生成します。
    • ペンの現在地 (x,y) から近い位置(最大でもxとyに関してlmaxの距離)として選ばれることに注意して下さい。
    • さらに、この段階では、(xnew, ynew) はあくまでも移動先の「候補」であることに注意して下さい。
    • この「候補」に対して新しい線を結ぶ資格があるかを調べるために、移動先 (xnew,ynew) と円の中心 (x0,y0) からの距離(dis)を計算しています。
  • (38-42行目)
    • while文です。「dis > r」すなわち、移動先の候補が円の外部にあった場合、もう一度候補をつくり直します。 新たにつくった候補がまた円の外部にあったら?またつくり直します。「dis > r」が成立しなくなるまで(すなわち、「dis<=r」が成立するまで)、何度でもやります。
  • (45行目)
    • while文を抜けたといういことは、(xnew,ynew) は円の内部の点であるということですので、新しい線分を円の内部で引く準備ができました。
    • lineTo関数で、新たな点 (xnew,ynew) に線を引きます。
  • (47行目)
    • (xnew, ynew) を現在のペンの位置 (x,y) として更新します。

返り値のない関数の追加

先のコードの中身を見ると、33-35行と39-41行で、 同一の処理が記述されている場所があります。 このような場合、同一の処理群を一つの関数としてまとめると、コードの視認性が格段に高まります。

まず、script.jsの末尾(60行目以降)に以下の関数を追加してみましょう。

これによって「makeNewPoints」という名前の関数が、引数なしでつくられています。 これによって、33-34行と39-40行の記述は、それぞれmakeNewPoints()の1行で済ませることができます。 以下は、makeNewPoints()を使って、32-48行目を書き換えたものです。

返り値のある関数の追加

次に、35行目(41行目)の処理も、同一の関数「makeNewPoint」の中に追加してみましょう。 この際、35行目と41行目で使用されている変数disはローカル変数のため、 この変数disに対して、必要な計算結果を返す関数へと拡張する必要があります。 今回の場合、 makeNewPoint関数を実行することによって、新しい座標の候補を計算することに加え、 その座標候補と円の中心との距離の値を返す仕様に変更しましょう。

このように処理の追加された関数「makeNewPoints()」は、 xnewとynewを更新した後、(xnew,ynew)と円の中心との距離dを計算し、その「値を返します」。 関数の中にreturn文が入っている場合、その関数はreturn文に指定された値を返すことができます。 関数が「値を返す」とは、具体的には、次のような代入文において、

var x = func();

まず、func()関数の内部の処理が実行された後に(式の評価)、 左辺の変数の値が、func()の中のreturn文で指定された値に変更される事態を指します。 なお、関数処理において、return文以降の処理は無視されることに注意して下さい(ちょうど繰り返し文におけるbreak文のような役割を果たします)。

このmakeNewPoints()を用いると、32-48行のfor文は、次のように簡略化できます。

引数のある関数の追加

最後に、オリジナルファイルの27-50行までを一気に関数化するdrawLineCirlce関数をつくりましょう。 ただし、今回の関数は引数を一つ持っています。この定義では、引数を変数nで表現していることに注意して下さい。 この変数nはfor文の中の繰り返し回数に対応しています。

上の関数定義のように、引数は

というような形で関数名に引き続く括弧の中で、コンマを挟んで複数指定することができます。

このように引数を使う関数は、次のように、実行時に引数の値を具体的に決めてやる必要があります。

これにより、引数に関連づけられた特定の変数の値を保留にしたまま、関数の定義を記述することができます。 例えば、今回のケースでは、線を何本(n)連結するかは、関数の実行時に決めればよいことになります。

修正したコード

関数「makeNewPoints」と「drawLineCircle(n)」を使った新しいコードを以下に表示します(コピーできます)。 draw()における、円を描く処理の部分(26-28)が非常にすっきりと記述されていることがわかります。

9.3 課題

関数の恩恵は、何回も「同じ処理セット」を呼び出す必要がある場合にこそ生まれます。 せっかく「drawLineCircle(n)」の関数をつくったので、これを使って、たくさんの円を描いてみましょう。

元ファイルのダウンロード

  • ダウンロード
  • sample9B.zipを解凍すると, sample9Bのフォルダの中にwork.htmlとscript.jsの2つのファイルが入っています。
  • work.htmlの中でcanvasのサイズを変更することができます。
  • ファイル名を変える必要はありませんが、課題に応じてフォルダ名(sample9BX, sample9BY, sample9BZ)を変えて下さい。

課題1(sample9BX)

  • script.jsの26-28行目以降に、処理を追加し、円の上下左右に小さい半径の円を4つ追加して下さい(新しい円の半径は自由とします)。
  • フォルダ名をsample9BXに変更して、フォルダごと提出して下さい。

    - - - - - 実行例(クリックしてください) - - - - - - - -

    Drawing

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

課題2(sample9BY)

  • script.jsの26-28行を改変し、同じ半径の円が、キャンバス全体にランダムに出現するようにしてください。
  • 必要に応じてlmax(線分の長さ)を変えて下さい(7行目)。
  • for文を使うといいでしょう。
  • フォルダ名をsample9BYに変更して、フォルダごと提出して下さい。

    - - - - - 実行例(クリックしてください) - - - - - - - -

    Drawing

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

発展

  • 円の位置を徐々にずらして重ねたり、色を連続的に変化させることで、単体では得られない面白い効果を得ることができます。試してみて下さい。

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2014-01-08 (水) 06:54:19 (1897d)