034.3 レッスン 2
Certificate: |
Web開発の要点 |
---|---|
Version: |
1.0 |
Topic: |
034 JavaScriptプログラミング |
Objective: |
034.3 JavaScriptの制御構造と関数 |
Lesson: |
2 of 2 |
はじめに
開発者は、JavaScript言語が提供する標準的な組み込み関数に加えて、独自のカスタム関数を記述することで、アプリケーションのニーズに適した入力と出力を対応付けることができます。カスタム関数は、基本的に、式の一部として他の場所で使用するためにカプセル化されたステートメントのセットです。
関数はプログラム中のさまざまな場所から呼び出すことができるので、重複したコードを書かないためには、関数を使うのがよい方法です。さらに、ステートメントを関数にまとめることで、JavaScriptプログラミングの中心的な要素であるイベントへのカスタムアクションの結合が容易になります。
関数の定義
プログラムが大きくなってくると、関数を使わずに何をしているのかを整理することが難しくなります。関数はそれぞれプライベートな変数スコープを持っているので、関数の中で定義された変数は、その関数の中でしか使えません。そのため、他の関数の変数と混ざってしまうことはありません。グローバル変数は関数内からもアクセスできますが、入力値を関数に送るには 関数パラメータ を使うのが望ましいでしょう。例として、前のレッスンで習った素数検証器を使ってみましょう。
// A naive prime number tester
// The number we want to evaluate
let candidate = 231;
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Display the result in the console window
if ( is_prime )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
コードの後半で、ある数字が素数であるかどうかを調べる必要がある場合、すでに書かれたコードを繰り返す必要があります。元のコードに修正や改良が加えられた場合、コードがコピーされた先でも手作業で再現する必要があるため、このような方法はお勧めできません。また、コードを繰り返すことで、ブラウザやネットワークに負荷がかかり、Webページの表示速度が低下する可能性があります。このような方法ではなく、適切なステートメントを関数に移動させてください。
// A naive prime number tester function
function test_prime(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
関数の宣言は、 function
文で始まり、その後に関数の名前とパラメータが続きます。関数の名前は、変数の名前と同じルールに従わなければなりません。関数のパラメータは、関数の 引数 とも呼ばれ、カンマで区切られ、括弧で囲まれています。
Tip
|
関数の宣言に引数を記載することは必須ではありません。関数に渡された引数は、その関数内の配列のような |
この例では、 test_prime
関数の引数は1つだけで、それは、テストする素数の候補である candidate
引数です。関数の引数は変数のように機能しますが、その値は関数を呼び出したステートメントによって割り当てられます。例えば、 test_prime(231)
という文は、 test_prime
関数を呼び出し、 candidate
引数に231という値を代入します。この値は、通常の変数のように、関数本体内で利用できます。
呼び出し文が関数のパラメータに単純変数を使用している場合、その値は関数の引数にコピーされます。呼び出し文で使われているパラメータの値を、関数内で使われているパラメータにコピーするこの手順を、 引数の値渡し と呼びます。関数が引数を変更しても、呼び出し文で使われていた元の変数には影響しません。しかし、呼び出し文が関数のパラメータに複雑なオブジェクト(つまり、プロパティやメソッドが付加されたオブジェクト)を引数として使用している場合、それらは 参照渡し となり、関数は呼び出し文で使用していた元のオブジェクトを変更することができてしまいます。
値で渡された引数や、関数内で宣言された変数は、関数の外では見ることができません。つまり、それらのスコープは、宣言された関数の本体に限定されます。それにもかかわらず、関数は通常、関数の外に見える何らかの出力を作成するために採用されます。呼び出した関数と値を共有するために、関数は return
文を定義します。
例えば、前の例の test_prime
関数は、 is_prime
変数の値を返します。したがって、この関数は、元の例で使われていた変数をどこでも置き換えることができます。
// The number we want to evaluate
let candidate = 231;
// Display the result in the console window
if ( test_prime(candidate) )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
return
文は、その名のとおり、呼び出した関数に制御を戻すものです。したがって、関数内のどこに return
文を置いても、それに続くものは何も実行されません。1つの関数に複数の return
文を含めることができます。この方法は、いくつかのステートメントが条件付きのブロックの中にある場合に便利で、関数が実行されるたびに特定の return
ステートメントを実行することもあれば、しないこともあります。
関数の中には、値を返さないものもありますので、 return
文は必須ではありません。関数の内部ステートメントは、その有無にかかわらず実行されますので、関数は、例えば、グローバル変数の値や、参照渡しされたオブジェクトの内容を変更するためにも使用できます。それにもかかわらず、関数が return
文を持たない場合、そのデフォルトの戻り値は undefined
に設定されます。これは、値を持たず、書き込むこともできない予約済みの変数です。
関数式
JavaScriptでは、関数は オブジェクト の一種です。そのため、関数は変数のようにスクリプト内で使用することができます。この特性は、 関数式 と呼ばれる別の構文を使って関数を宣言したときに明示されます。
let test_prime = function(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
この例と前の例の関数宣言との唯一の違いは、最初の行にあります。 function test_prime(candidate)
ではなく、let test_prime = function(candidate)
となっていることです。関数式の中で、 test_prime
という名前は、関数を含むオブジェクトに使われ、関数自体の名前には使われません。関数式で定義された関数は、宣言構文で定義された関数と同じように呼び出されます。ただし、宣言された関数は宣言の前後を問わずに呼び出すことができるのに対し、関数式は初期化後にのみ呼び出すことができます。変数と同様に、式で定義された関数を初期化前に呼び出すと、参照エラーが発生します。
関数の再帰
カスタム関数は、ステートメントの実行や組み込み関数の呼び出しに加えて、自分自身を含む他のカスタム関数を呼び出すことができます。自分自身から関数を呼び出すことを 再帰関数 と呼びます。解決しようとしている問題の種類によっては、再帰関数を使った方が、ネストしたループを使って反復的な作業を行うよりも簡単な場合があります。
ここまでで、与えられた数が素数かどうかを調べるための関数の使い方がわかりました。ここで、与えられた数の次の素数を見つけたいとします。 while
ループを使って候補の数字を増やし、その候補の因数を探す入れ子のループを書くことができます。
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "true" keeps the loop going until a prime is found
while ( true )
{
// Auxiliary control variable
let is_prime = true;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Test the next candidate
is_prime = false;
break;
}
}
// End loop and return candidate if it is prime
if ( is_prime )
{
return candidate;
}
// If prime not found yet, try the next odd number
candidate = candidate + 2;
}
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
注意していただきたいのは、 while
ループに一定の条件(括弧内の true
式)と、ループを止めるタイミングを知るための補助変数 is_prime
を使う必要があることです。この解決法は正しいのですが、入れ子になったループを使うことは、同じタスクを実行するために再帰を使うことほどエレガントではありません。
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Call the next_prime function recursively, this time
// using the failed candidate as the argument.
return next_prime(candidate);
}
}
// "candidate" is not divisible by any integer factor other
// than 1 and itself, therefore it is a prime number.
return candidate;
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
next_prime
のどちらのバージョンも、唯一の引数として与えられた数( from
)の次の素数を返します。再帰的なバージョンでは、前のバージョンと同様に、特殊なケース(すなわち、2以下の数)をチェックすることから始まります。その後、候補を増やしていき、 for
ループで因数を探し始めます( while
ループがなくなっていることに注意してください)。この時点で、唯一の偶数の素数はすでにテストされているので、候補とその可能な因子を2つ増やしていきます。(奇数に2を加えたものが次の奇数になります)。
例題の for
ループから抜け出す方法は2つしかありません。可能性のある因数をすべて試して、候補を割ったときの余りが0になるものがなければ、 for
ループは終了し、関数は候補を from
の次の素数として返します。そうでなければ、 factor
が candidate
の整数倍である( candidate % factor == 0
)場合には、 next_prime
関数が再帰的に呼び出され、今度は増加した candidate
を from
パラメータとして用いて返されます。 next_prime
の呼び出しは、1つの候補が最終的に因数を見つけられなくなるまで、互いに積み重ねられます。そして、素数を含む最後の next_prime
インスタンスは、その素数を前の next_prime
インスタンスに返し、その結果、最初の next_prime
インスタンスまで連続していきます。関数の各呼び出しでは、変数に同じ名前を使っていますが、呼び出しは互いに分離されているので、それらの変数はメモリ内で分離されています。
演習
-
関数を使うことで、開発者はどのようなオーバーヘッドを軽減できるのでしょうか?
-
関数の引数が値で渡される場合と、関数の引数が参照で渡される場合の違いは何ですか?
-
カスタム関数にreturn文がない場合、どの値が出力として使われますか?
発展演習
-
関数を呼び出した際に発生した 不適切な参照エラー(Uncaught Reference Error) の原因は何ですか?
-
factor
、from
、およびto
の3つの引数を受け取るmultiples_of
という関数を書いてください。この関数の中で、console.log()
という命令を使って、from
とto
の間にあるfactor
のすべての倍数を表示してください。
まとめ
このレッスンでは、JavaScriptのコードにカスタム関数を記述する方法を説明しました。カスタム関数を使うと、開発者はアプリケーションを再利用可能なコードの “チャンク” に分割することができ、大規模なプログラムの作成やメンテナンスが容易になります。このレッスンでは、次のような概念と手順を説明しました。
-
カスタム関数を定義する方法:関数宣言と関数式。
-
関数の入力にパラメータを使用する
-
関数の出力を設定するために
return
文を使用する -
関数の再帰
演習の回答
-
関数を使うことで、開発者はどのようなオーバーヘッドを軽減できるのでしょうか?
関数を使うとコードを再利用できるので、コードのメンテナンスが容易になります。また、スクリプトファイルを小さくすることで、メモリやダウンロードの時間を節約することができます。
-
関数の引数が値で渡される場合と、関数の引数が参照で渡される場合の違いは何ですか?
値で渡された場合、引数は関数にコピーされ、関数は呼び出し文の元の変数を変更することができません。参照で渡された場合、関数は呼び出し文で使われた元の変数を操作することができます。
-
カスタム関数にreturn文がない場合、どの値が出力として使われますか?
返された値は
undefined
に設定されます。
発展演習の回答
-
関数を呼び出した際に発生した 不適切な参照エラー(Uncaught Reference Error) の原因は何ですか?
スクリプトファイルで宣言される前に関数が呼び出されました。
-
factor
、from
、およびto
の3つの引数を受け取るmultiples_of
という関数を書いてください。この関数の中で、console.log()
という命令を使って、from
とto
の間にあるfactor
のすべての倍数を表示してください。function multiples_of(factor, from, to) { for ( let number = from; number <= to; number++ ) { if ( number % factor == 0 ) { console.log(factor, "*", number / factor, "=", number); } } }