2022年10月14日

関数

スクリプトの多くの場所で同様の処理を実行する必要があることがよくあります。

たとえば、訪問者がログインしたり、ログアウトしたり、あるいは他の場所で、見栄えの良いメッセージを表示する必要があるとします。

関数はプログラムの主要な「構成要素」です。これにより、コードを繰り返し記述することなく何度も呼び出すことができます。

すでに`alert(message)`、`prompt(message, default)`、`confirm(question)`などの組み込み関数の例を見てきました。しかし、自分自身で関数を作成することもできます。

関数宣言

関数を作成するには、*関数宣言*を使用できます。

それは次のようになります

function showMessage() {
  alert( 'Hello everyone!' );
}

`function`キーワードが最初にあり、次に*関数の名前*、次に括弧内の*パラメーター*のリスト(コンマで区切られ、上の例では空です。後ほど例を示します)、最後に中括弧内の関数のコード、つまり「関数本体」が続きます。

function name(parameter1, parameter2, ... parameterN) {
 // body
}

新しい関数は、その名前で呼び出すことができます:`showMessage()`。

例えば

function showMessage() {
  alert( 'Hello everyone!' );
}

showMessage();
showMessage();

`showMessage()`呼び出しは、関数のコードを実行します。ここでは、メッセージが2回表示されます。

この例は、関数の主要な目的の1つであるコードの重複を避けることを明確に示しています。

メッセージや表示方法を変更する必要がある場合、出力する関数のコードを1箇所修正するだけで済みます。

ローカル変数

関数内で宣言された変数は、その関数内でのみ見えます。

例えば

function showMessage() {
  let message = "Hello, I'm JavaScript!"; // local variable

  alert( message );
}

showMessage(); // Hello, I'm JavaScript!

alert( message ); // <-- Error! The variable is local to the function

外部変数

関数は外部変数にもアクセスできます。例えば

let userName = 'John';

function showMessage() {
  let message = 'Hello, ' + userName;
  alert(message);
}

showMessage(); // Hello, John

関数は外部変数に完全にアクセスできます。また、それを変更することもできます。

例えば

let userName = 'John';

function showMessage() {
  userName = "Bob"; // (1) changed the outer variable

  let message = 'Hello, ' + userName;
  alert(message);
}

alert( userName ); // John before the function call

showMessage();

alert( userName ); // Bob, the value was modified by the function

ローカル変数がない場合にのみ、外部変数が使用されます。

同じ名前の変数が関数内で宣言されている場合、それは外部変数を*シャドウ*します。例えば、以下のコードでは、関数はローカルの`userName`を使用します。外部のものは無視されます

let userName = 'John';

function showMessage() {
  let userName = "Bob"; // declare a local variable

  let message = 'Hello, ' + userName; // Bob
  alert(message);
}

// the function will create and use its own userName
showMessage();

alert( userName ); // John, unchanged, the function did not access the outer variable
グローバル変数

上のコードの外部`userName`のように、どの関数外にも宣言されていない変数を*グローバル*変数と呼びます。

グローバル変数は、(ローカル変数によってシャドウされていない限り)どの関数からも見えます。

グローバル変数の使用を最小限に抑えるのが良い習慣です。最新のコードには、グローバル変数がほとんど、または全くありません。ほとんどの変数は、それらの関数内に存在します。しかし、プロジェクトレベルのデータを格納するために役立つ場合があります。

パラメーター

パラメーターを使用して、任意のデータを関数に渡すことができます。

以下の例では、関数は2つのパラメーター`from`と`text`を持っています。

function showMessage(from, text) { // parameters: from, text
  alert(from + ': ' + text);
}

showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)

行`(*)`と`(**)`で関数が呼び出されると、与えられた値はローカル変数`from`と`text`にコピーされます。その後、関数はそれらを使用します。

もう1つの例を挙げましょう。変数`from`があり、それを関数に渡します。注意してください。関数は`from`を変更しますが、その変更は外部では見られません。なぜなら、関数は常に値のコピーを取得するからです。

function showMessage(from, text) {

  from = '*' + from + '*'; // make "from" look nicer

  alert( from + ': ' + text );
}

let from = "Ann";

showMessage(from, "Hello"); // *Ann*: Hello

// the value of "from" is the same, the function modified a local copy
alert( from ); // Ann

値が関数パラメーターとして渡されると、それは*引数*とも呼ばれます。

言い換えれば、これらの用語を明確にするために

  • パラメーターは、関数宣言の括弧内にリストされている変数です(宣言時の用語です)。
  • 引数は、関数が呼び出されたときに関数に渡される値です(呼び出し時の用語です)。

パラメーターをリストアップして関数を宣言し、引数を渡して呼び出します。

上記の例では、「関数`showMessage`は2つのパラメーターで宣言され、次に2つの引数`from`と`"Hello"`で呼び出されます」と言うことができます。

デフォルト値

関数が呼び出されても引数が提供されない場合、対応する値は`undefined`になります。

例えば、前述の関数`showMessage(from, text)`は、1つの引数で呼び出すことができます

showMessage("Ann");

これはエラーではありません。このような呼び出しでは`"*Ann*: undefined"`が出力されます。`text`の値が渡されないため、`undefined`になります。

`=`を使用して、関数宣言でパラメーターのいわゆる「デフォルト」(省略された場合に使用)値を指定できます。

function showMessage(from, text = "no text given") {
  alert( from + ": " + text );
}

showMessage("Ann"); // Ann: no text given

これで、`text`パラメーターが渡されないと、`"no text given"`という値が取得されます。

デフォルト値は、パラメーターが存在しても厳密に`undefined`である場合にも、次のように挿入されます。

showMessage("Ann", undefined); // Ann: no text given

ここでは`"no text given"`は文字列ですが、より複雑な式にすることもできます。これは、パラメーターが欠落している場合にのみ評価され、割り当てられます。そのため、これも可能です。

function showMessage(from, text = anotherFunction()) {
  // anotherFunction() only executed if no text given
  // its result becomes the value of text
}
デフォルトパラメーターの評価

JavaScriptでは、デフォルトパラメーターは、それぞれの引数が渡されずに関数が呼び出されるたびに評価されます。

上記の例では、`text`パラメーターが提供されている場合、`anotherFunction()`はまったく呼び出されません。

一方、`text`が欠落しているたびに、独立して呼び出されます。

古いJavaScriptコードでのデフォルトパラメーター

数年前、JavaScriptはデフォルトパラメーターの構文をサポートしていませんでした。そのため、人々はそれらを指定するために他の方法を使用していました。

現在では、古いスクリプトで見かけることがあります。

たとえば、`undefined`に対する明示的なチェック

function showMessage(from, text) {
  if (text === undefined) {
    text = 'no text given';
  }

  alert( from + ": " + text );
}

…または`||`演算子を使用する

function showMessage(from, text) {
  // If the value of text is falsy, assign the default value
  // this assumes that text == "" is the same as no text at all
  text = text || 'no text given';
  ...
}

代替デフォルトパラメーター

関数宣言後、後でパラメーターのデフォルト値を割り当てるのが理にかなう場合があります。

`undefined`と比較することで、関数の実行中にパラメーターが渡されたかどうかを確認できます。

function showMessage(text) {
  // ...

  if (text === undefined) { // if the parameter is missing
    text = 'empty message';
  }

  alert(text);
}

showMessage(); // empty message

…または`||`演算子を使用できます。

function showMessage(text) {
  // if text is undefined or otherwise falsy, set it to 'empty'
  text = text || 'empty';
  ...
}

最新のJavaScriptエンジンはNullish 合体演算子`??`をサポートしており、`0`などのほとんどのfalsy値を「標準」と見なす必要がある場合に適しています。

function showCount(count) {
  // if count is undefined or null, show "unknown"
  alert(count ?? "unknown");
}

showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown

値の返却

関数は、結果として呼び出し側のコードに値を返すことができます。

最も簡単な例は、2つの値を合計する関数です。

function sum(a, b) {
  return a + b;
}

let result = sum(1, 2);
alert( result ); // 3

`return`ディレクティブは、関数のどこにでも配置できます。実行がそれに達すると、関数は停止し、値が呼び出し側のコードに返されます(上記では`result`に割り当てられます)。

1つの関数に`return`が複数回出現する可能性があります。例えば

function checkAge(age) {
  if (age >= 18) {
    return true;
  } else {
    return confirm('Do you have permission from your parents?');
  }
}

let age = prompt('How old are you?', 18);

if ( checkAge(age) ) {
  alert( 'Access granted' );
} else {
  alert( 'Access denied' );
}

値なしで`return`を使用することもできます。これにより、関数はすぐに終了します。

例えば

function showMovie(age) {
  if ( !checkAge(age) ) {
    return;
  }

  alert( "Showing you the movie" ); // (*)
  // ...
}

上記のコードでは、`checkAge(age)`が`false`を返す場合、`showMovie`は`alert`に進みません。

空の`return`またはそれのない関数は`undefined`を返します

関数が値を返さない場合、`undefined`を返す場合と同じです

function doNothing() { /* empty */ }

alert( doNothing() === undefined ); // true

空の`return`は`return undefined`と同じです

function doNothing() {
  return;
}

alert( doNothing() === undefined ); // true
`return`と値の間に改行を追加しないでください

`return`の長い式では、次のように別の行に配置したくなるかもしれません。

return
 (some + long + expression + or + whatever * f(a) + f(b))

これは機能しません。JavaScriptは`return`の後にセミコロンがあると想定しているためです。これは次のと同じように機能します。

return;
 (some + long + expression + or + whatever * f(a) + f(b))

したがって、事実上、空のreturnになります。

返された式を複数行に渡してラップしたい場合は、`return`と同じ行で開始する必要があります。または、少なくとも、次の行のように開始括弧をそこに配置します。

return (
  some + long + expression
  + or +
  whatever * f(a) + f(b)
  )

そして、期待通りに動作します。

関数の命名

関数は動作です。そのため、その名前は通常動詞です。簡潔で、できるだけ正確であり、関数が何をしているかを説明するものでなければなりません。そうすることで、コードを読む人が関数が何をしているのかを知る手がかりを得ることができます。

動作を漠然と説明する動詞の接頭辞で関数を始めるのが一般的な慣習です。接頭辞の意味については、チーム内で合意が必要です。

たとえば、「show」で始まる関数は通常、何かを表示します。

…で始まる関数

  • `"get…"` – 値を返す、
  • `"calc…"` – 何かを計算する、
  • `"create…"` – 何かを作成する、
  • `"check…"` – 何かをチェックしてブール値を返すなど。

そのような名前の例

showMessage(..)     // shows a message
getAge(..)          // returns the age (gets it somehow)
calcSum(..)         // calculates a sum and returns the result
createForm(..)      // creates a form (and usually returns it)
checkPermission(..) // checks a permission, returns true/false

接頭辞を使用すると、関数の名前を一瞥するだけで、どのような作業を行い、どのような値を返すのかがわかります。

1つの関数 – 1つのアクション

関数は、その名前で示唆されているとおりにのみ動作する必要があります。それ以上ではありません。

2つの独立したアクションは、通常一緒に呼び出される場合でも(その場合、それらを呼び出す第3の関数を作成できます)、通常は2つの関数に値します。

この規則を破るいくつかの例

  • `getAge` – 年齢とともに`alert`を表示する場合、これは悪いでしょう(取得するだけにする必要があります)。
  • `createForm` – ドキュメントを変更してフォームを追加する場合、これは悪いでしょう(作成して返すだけにする必要があります)。
  • checkPermissionアクセス許可/拒否メッセージを表示するのは良くありません(チェックを実行して結果を返すだけにすべきです)。

これらの例では、接頭辞の一般的な意味を想定しています。あなたとあなたのチームは、他の意味について合意できますが、通常はそれほど違いはありません。いずれにしても、接頭辞の意味、接頭辞付き関数の機能と非機能をしっかりと理解する必要があります。同じ接頭辞を持つすべての関数は、そのルールに従う必要があります。そして、チームはその知識を共有する必要があります。

超短い関数名

非常に頻繁に使用される関数は、超短い名前を持つことがあります。

例えば、jQueryフレームワークは$という名前の関数を定義しています。Lodashライブラリには、_という名前のコア関数があります。

これらは例外です。一般的に、関数名は簡潔で記述的であるべきです。

関数 == コメント

関数は短く、正確に1つのことだけを行うべきです。それが大きなタスクである場合、関数をいくつかの小さな関数に分割する価値があるかもしれません。このルールに従うのが難しい場合もありますが、間違いなく良いことです。

個別の関数は、テストとデバッグが容易になるだけでなく、その存在自体が優れたコメントになります!

例えば、以下のshowPrimes(n)という2つの関数を比較してみましょう。どちらもnまでの素数を出力します。

最初の変種はラベルを使用しています

function showPrimes(n) {
  nextPrime: for (let i = 2; i < n; i++) {

    for (let j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }

    alert( i ); // a prime
  }
}

2番目の変種は、素数判定のための追加関数isPrime(n)を使用しています

function showPrimes(n) {

  for (let i = 2; i < n; i++) {
    if (!isPrime(i)) continue;

    alert(i);  // a prime
  }
}

function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if ( n % i == 0) return false;
  }
  return true;
}

2番目の変種の方が理解しやすいですね。コードのかたまりではなく、アクションの名前(isPrime)が表示されます。このようなコードを自己記述的と呼ぶ人もいます。

したがって、再利用するつもりがない場合でも、関数は作成できます。それらはコードの構造を明確にし、可読性を向上させます。

概要

関数宣言は次のようになります

function name(parameters, delimited, by, comma) {
  /* code */
}
  • 関数にパラメーターとして渡された値は、そのローカル変数にコピーされます。
  • 関数は外部変数にアクセスできます。しかし、それは内側から外へ向かう方向にのみ機能します。関数の外部のコードは、そのローカル変数を参照できません。
  • 関数は値を返すことができます。返さない場合は、その結果はundefinedになります。

コードをクリーンで理解しやすくするために、関数内では主にローカル変数とパラメーターを使用し、外部変数を使用しないことをお勧めします。

パラメーターを取得し、それらを使用して結果を返す関数の方が、パラメーターを取得せずに外部変数を副作用として変更する関数よりも、常に理解しやすいです。

関数名の付け方

  • 名前は、関数が何をするかを明確に記述する必要があります。コード内で関数呼び出しを見ると、適切な名前はすぐにそれが何を行い、何を返すかを理解させてくれます。
  • 関数はアクションなので、関数名は通常動詞です。
  • create…show…get…check…など、多くのよく知られた関数接頭辞があります。それらを使用して、関数の機能を示唆しましょう。

関数はスクリプトの主要な構成要素です。これで基本事項を網羅したので、実際にそれらを作成して使用し始めることができます。しかし、それは道の始まりに過ぎません。私たちは何度もそれらに戻り、高度な機能をより深く掘り下げていきます。

課題

重要度: 4

次の関数は、パラメーターage18より大きい場合にtrueを返します。

そうでない場合は、確認を求めてその結果を返します。

function checkAge(age) {
  if (age > 18) {
    return true;
  } else {
    // ...
    return confirm('Did parents allow you?');
  }
}

elseを削除した場合、関数の動作は異なりますか?

function checkAge(age) {
  if (age > 18) {
    return true;
  }
  // ...
  return confirm('Did parents allow you?');
}

これら2つのバリアントの動作に違いはありますか?

違いはありません!

どちらの場合も、if条件が偽の場合にのみreturn confirm('Did parents allow you?')が実行されます。

重要度: 4

次の関数は、パラメーターage18より大きい場合にtrueを返します。

そうでない場合は、確認を求めてその結果を返します。

function checkAge(age) {
  if (age > 18) {
    return true;
  } else {
    return confirm('Did parents allow you?');
  }
}

ifを使用せずに、1行で同じ動作をするように書き直してください。

checkAgeの2つのバリアントを作成します。

  1. 疑問符演算子?を使用する
  2. OR ||を使用する

疑問符演算子'?'を使用する

function checkAge(age) {
  return (age > 18) ? true : confirm('Did parents allow you?');
}

OR ||を使用する(最も短いバリアント)

function checkAge(age) {
  return (age > 18) || confirm('Did parents allow you?');
}

age > 18周りの括弧はここでは必須ではありません。可読性を高めるために存在しています。

重要度: 1

2つの数値abのうち、小さい方を返す関数min(a,b)を作成してください。

例えば

min(2, 5) == 2
min(3, -1) == -1
min(1, 1) == 1

ifを使用した解答

function min(a, b) {
  if (a < b) {
    return a;
  } else {
    return b;
  }
}

疑問符演算子'?'を使用した解答

function min(a, b) {
  return a < b ? a : b;
}

補足:a == bの場合、どちらを返しても構いません。

重要度: 4

xn乗を返す関数pow(x,n)を作成してください。言い換えれば、xを自身でn回掛け合わせて結果を返します。

pow(3, 2) = 3 * 3 = 9
pow(3, 3) = 3 * 3 * 3 = 27
pow(1, 100) = 1 * 1 * ...* 1 = 1

xnを要求し、pow(x,n)の結果を表示するウェブページを作成してください。

デモを実行する

補足:この課題では、関数は1以上の整数である自然数のnの値のみをサポートする必要があります。

function pow(x, n) {
  let result = x;

  for (let i = 1; i < n; i++) {
    result *= x;
  }

  return result;
}

let x = prompt("x?", '');
let n = prompt("n?", '');

if (n < 1) {
  alert(`Power ${n} is not supported, use a positive integer`);
} else {
  alert( pow(x, n) );
}
チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善点の提案がある場合は、コメントする代わりにGitHub issueまたはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 数行のコードを挿入するには<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックス(plnkrjsbincodepen…)を使用してください。