JavaScript基礎 #4 関数

読了 6分

#3 制御フロー で分岐と繰り返しを見たので、今回は — 再利用可能なコード単位 を作る道具です。JavaScriptの関数。

関数宣言 (Function Declaration) #

最も伝統的な形です。

関数宣言
function add(a, b) {
  return a + b;
}

add(2, 3);   // 5

キーワード function で始まり、名前を持ちます。最大の特徴は ホイスティング(後ほど説明)で、コードのどこから呼び出しても動作する点です。

関数式 (Function Expression) #

関数を 値として 扱う形です。

関数式
const add = function(a, b) {
  return a + b;
};

add(2, 3);   // 5

function キーワードが式の右側にあります。変数 add に関数が入ったかたちで、変数と同じルールで動作します(ブロックスコープ、ホイスティングされない)。

アロー関数 (Arrow Function) #

ES2015で追加された短い形です。コールバックのような短い関数によく使います。

アロー関数
const add = (a, b) => a + b;
const square = (n) => n * n;
const greet = (name) => {
  console.log(`こんにちは、${name}!`);
};

add(2, 3);   // 5
square(5);   // 25
greet('カーティス');

文法のまとめ:

  • 本体が式1つなら { return ... } を省略可能
  • パラメータが1つなら括弧を省略可能 (n => n * 2)
  • パラメータが0個または2個以上なら括弧は必須

アロー関数と通常の関数の違い — this #

最大の違いは this の動作 です。通常の関数は呼び出し方によって this が変わりますが、アロー関数は 宣言された場所の this をそのまま引き継ぎます

this の違い — コールバックでの意味
const obj = {
  name: 'カーティス',
  greetLater() {
    setTimeout(function() {
      console.log(this.name);    // undefined (this が obj ではない)
    }, 100);
  },
  greetLaterArrow() {
    setTimeout(() => {
      console.log(this.name);    // 'カーティス' (外側の this を維持)
    }, 100);
  },
};

この違いから、コールバック関数はアロー関数オブジェクトのメソッドは通常の関数 あるいはメソッドの短縮構文を使うのが一般的なコンベンションです。this上級シリーズで深く 扱います。

どれをいつ使うのですか? #

実務でよく使われるガイド:

  • 関数宣言 — モジュールの最上位に置く意味のある関数 (function calculate(...))
  • アロー関数 — 短いコールバック、変換、計算 (map((x) => x * 2))
  • 関数式 — アローがなかった時代の名残。新しいコードではほとんど使わない

このシリーズもそのガイドに従います。

仮引数 — デフォルト値 #

仮引数にデフォルト値を与えることができます。

デフォルト値の仮引数
function greet(name = '匿名') {
  console.log(`こんにちは、${name}!`);
}

greet();           // こんにちは、匿名!
greet('カーティス');    // こんにちは、カーティス!
greet(undefined);  // こんにちは、匿名!  ← undefined もデフォルト値発動
greet(null);       // こんにちは、null!  ← null はそのまま

undefined はデフォルト値を発動させますが、null はそのまま渡されます。「値が決まっていない」の2つの意味(#2)が、ここでは異なる動作を生みます。

仮引数 — 可変長引数 (...rest) #

引数をいくつ受け取るか分からないとき、... で集めることができます。

rest 仮引数
function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}

sum(1, 2);              // 3
sum(1, 2, 3, 4, 5);     // 15
sum();                   // 0

...numbers残りの引数を配列に まとめます。最後の仮引数にだけ使えます。

通常の引数と一緒に #

前は明示、後ろは rest
function logCall(label, ...args) {
  console.log(`[${label}]`, ...args);
}

logCall('debug', 1, 2, 3);
// [debug] 1 2 3

呼び出し側の ...argsスプレッド(spread) と呼ばれます。同じ ... 構文ですが、置かれる位置によって意味が異なります。この2つは #5 オブジェクトと配列 で改めて扱います。

戻り値 — return #

関数が値を返すキーワードです。

return
function double(n) {
  return n * 2;
}

double(5);   // 10

return に出会うと、関数は 即座に終了 します。その後ろのコードは実行されません。

早期リターン (early return)
function describe(n) {
  if (n === 0) return 'ゼロ';
  if (n < 0) return '負の数';
  return '正の数';
}

早期リターンは、インデントを減らし、流れを直線化 してくれます。深い if/else よりほぼ常に読みやすいです。おすすめのパターンです。

return がなければ undefined #

return がない関数
function silent() {
  console.log('した');
  // return なし
}

const result = silent();
console.log(result);   // undefined

JavaScriptのすべての関数は何かを返します。明示的な return がない場合、undefined が自動で返されます。

ホイスティング — 関数宣言の特殊性 #

JavaScriptがコードを実行する前に、関数宣言と変数宣言を先に上に引き上げる動作 をホイスティング(hoisting)と呼びます。

関数宣言はホイスティングが 完全に 起こるので、コードのどこからでも呼び出せます。

ホイスティング — 関数宣言
console.log(add(2, 3));   // 5 — 宣言の上で呼び出しても動作

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

一方、関数式やアロー関数は 変数 なので、宣言行より前で使うとエラーになります。

ホイスティング — 関数式/アロー
console.log(add(2, 3));   // ✗ ReferenceError

const add = (a, b) => a + b;

ほとんどのコードは使う前に宣言するので、ホイスティングを意識することは少ないです。ただし、関数宣言だけが引き上げられる という事実は、古いコードを読むときに時々出会います。

クロージャ — 関数が自分の環境を持ち歩く #

関数の強力な特性を1つ先取りしておきましょう。

クロージャ — カウンタを作る
function createCounter() {
  let count = 0;

  return function() {
    count = count + 1;
    return count;
  };
}

const counter = createCounter();
counter();   // 1
counter();   // 2
counter();   // 3

createCounter が終わっているのに、その中の count生きていて、関数によって覚えられています。このように、関数が自分の生まれた環境(スコープ)を引きずって動く動作 をクロージャと呼びます。

クロージャは、JavaScriptの最も重要な概念のひとつで、コールバック・イベントハンドラ・hookの動作のすべてに含まれています。上級シリーズ で本格的に扱います。今は「関数が自分の変数を覚えている」とだけ押さえておいてください。

即時実行関数 (IIFE) — 古いパターン #

古いコードでよく見かける形を1つ。

IIFE — Immediately Invoked Function Expression
(function() {
  console.log('作成直後に実行');
})();

関数を定義してすぐに呼び出すパターンです。昔はモジュールがなかったので、変数のスコープを隔離するためのトリックとして使われていました。モジュールがある今ではほとんど使いません。古い資料を読んでいて出会ったら、「昔のモジュールの真似」くらいに理解しておけば大丈夫です。

まとめ #

今回の記事で整理した内容:

  • 関数の定義は3つ — 宣言 / 関数式 / アロー
  • アロー関数の最大の違いは、this が外側から入ってくること
  • 意味のある関数は宣言、短いコールバックはアロー
  • デフォルト値の仮引数 — undefined なら発動、null はそのまま
  • ...rest で可変長引数を集める
  • 早期リターン(early return)で流れを直線化
  • 関数宣言だけがホイスティング — 関数式は変数のルール
  • クロージャ: 関数が自分の環境を覚える

次の記事(#5 オブジェクトと配列)では、JavaScriptの2つの中核データ構造 — オブジェクトと配列の基本的な使い方、そしてspread/分割代入のようなモダンな構文を扱います。

X