|
| 1 | +# 第4章: 関数とクロージャ |
| 2 | + |
| 3 | +JavaScriptにおいて関数はオブジェクトの一種です。つまり、変数に代入したり、他の関数の引数として渡したり、関数から戻り値として返したりすることができます。この柔軟性が、JavaScriptの設計パターンの核心を担っています。 |
| 4 | + |
| 5 | +## 関数の定義(関数宣言 vs 関数式) |
| 6 | + |
| 7 | +JavaScriptには関数を定義する方法が主に2つあります。「関数宣言」と「関数式」です。これらは似ていますが、**巻き上げ(Hoisting)** の挙動が異なります。 |
| 8 | + |
| 9 | +### 1\. 関数宣言 (Function Declaration) |
| 10 | + |
| 11 | +古くからある定義方法です。スクリプトの実行前に読み込まれるため、定義する前の行から呼び出すことができます。 |
| 12 | + |
| 13 | +```js:function_declaration.js |
| 14 | +console.log(greet("Alice")); // 定義前でも呼び出せる |
| 15 | + |
| 16 | +function greet(name) { |
| 17 | + return `Hello, ${name}!`; |
| 18 | +} |
| 19 | +``` |
| 20 | + |
| 21 | +```js-exec:function_declaration.js |
| 22 | +Hello, Alice! |
| 23 | +``` |
| 24 | + |
| 25 | +### 2\. 関数式 (Function Expression) |
| 26 | + |
| 27 | +変数に関数を代入するスタイルです。変数の代入は実行時に行われるため、定義する前に呼び出すとエラーになります。現代のJavaScript開発では、意図しない巻き上げを防ぐためにこちら(または後述のアロー関数)が好まれる傾向にあります。 |
| 28 | + |
| 29 | +```js:function_expression.js |
| 30 | +// 定義前に呼び出すと... ReferenceError: Cannot access 'sayHi' before initialization |
| 31 | +// console.log(sayHi("Bob")); |
| 32 | + |
| 33 | +const sayHi = function(name) { |
| 34 | + return `Hi, ${name}!`; |
| 35 | +}; |
| 36 | + |
| 37 | +console.log(sayHi("Bob")); |
| 38 | +``` |
| 39 | + |
| 40 | +```js-exec:function_expression.js |
| 41 | +Hi, Bob! |
| 42 | +``` |
| 43 | + |
| 44 | +## アロー関数 (=\>) の構文と特徴 |
| 45 | + |
| 46 | +ES2015 (ES6) で導入されたアロー関数は、関数式をより短く記述するための構文です。Javaのラムダ式やPythonのlambdaに似ていますが、いくつか独自の特徴があります。 |
| 47 | + |
| 48 | +### 基本構文 |
| 49 | + |
| 50 | +`function` キーワードを省略し、`=>` (矢印) を使って定義します。 |
| 51 | + |
| 52 | +```js:arrow_function.js |
| 53 | +// 従来の関数式 |
| 54 | +const add = function(a, b) { |
| 55 | + return a + b; |
| 56 | +}; |
| 57 | + |
| 58 | +// アロー関数 |
| 59 | +const addArrow = (a, b) => { |
| 60 | + return a + b; |
| 61 | +}; |
| 62 | + |
| 63 | +console.log(addArrow(3, 5)); |
| 64 | +``` |
| 65 | + |
| 66 | +```js-exec:arrow_function.js |
| 67 | +8 |
| 68 | +``` |
| 69 | + |
| 70 | +### 省略記法 |
| 71 | + |
| 72 | +アロー関数には強力な省略記法があります。 |
| 73 | + |
| 74 | +1. **引数が1つの場合**: カッコ `()` を省略可能。 |
| 75 | +2. **処理が1行でreturnする場合**: 中括弧 `{}` と `return` キーワードを省略可能(暗黙のreturn)。 |
| 76 | + |
| 77 | +```js-repl:4 |
| 78 | +> const square = x => x * x; // 引数の()とreturnを省略 |
| 79 | +> square(5); |
| 80 | +25 |
| 81 | +
|
| 82 | +> const getUser = (id, name) => ({ id: id, name: name }); // オブジェクトを返す場合は()で囲む |
| 83 | +> getUser(1, "Gemini"); |
| 84 | +{ id: 1, name: 'Gemini' } |
| 85 | +``` |
| 86 | + |
| 87 | +> **注意:** アロー関数は単なる短縮記法ではありません。「`this` を持たない」という重要な特徴がありますが、これについては**第5章**で詳しく解説します。 |
| 88 | +
|
| 89 | +## 引数:デフォルト引数、Restパラメータ (...) |
| 90 | + |
| 91 | +関数の柔軟性を高めるための引数の機能を見ていきましょう。 |
| 92 | + |
| 93 | +### デフォルト引数 |
| 94 | + |
| 95 | +引数が渡されなかった場合(または `undefined` の場合)に使用される初期値を設定できます。 |
| 96 | + |
| 97 | +```js:default_args.js |
| 98 | +const connect = (host = 'localhost', port = 8080) => { |
| 99 | + console.log(`Connecting to ${host}:${port}...`); |
| 100 | +}; |
| 101 | + |
| 102 | +connect(); // 両方省略 |
| 103 | +connect('127.0.0.1'); // portはデフォルト値 |
| 104 | +connect('example.com', 22); // 両方指定 |
| 105 | +``` |
| 106 | + |
| 107 | +```js-exec:default_args.js |
| 108 | +Connecting to localhost:8080... |
| 109 | +Connecting to 127.0.0.1:8080... |
| 110 | +Connecting to example.com:22... |
| 111 | +``` |
| 112 | + |
| 113 | +### Restパラメータ (残余引数) |
| 114 | + |
| 115 | +引数の数が不定の場合、`...` を使うことで、残りの引数を**配列として**受け取ることができます。以前は `arguments` オブジェクトを使っていましたが、Restパラメータの方が配列メソッド(`map`, `reduce`など)を直接使えるため便利です。 |
| 116 | + |
| 117 | +```js:rest_params.js |
| 118 | +const sum = (...numbers) => { |
| 119 | + // numbersは本物の配列 [1, 2, 3, 4, 5] |
| 120 | + return numbers.reduce((acc, curr) => acc + curr, 0); |
| 121 | +}; |
| 122 | + |
| 123 | +console.log(sum(1, 2, 3)); |
| 124 | +console.log(sum(10, 20, 30, 40, 50)); |
| 125 | +``` |
| 126 | + |
| 127 | +```js-exec:rest_params.js |
| 128 | +6 |
| 129 | +150 |
| 130 | +``` |
| 131 | + |
| 132 | +## スコープチェーンとレキシカルスコープ |
| 133 | + |
| 134 | +JavaScriptの変数の有効範囲(スコープ)を理解するために、「レキシカルスコープ」という概念を知る必要があります。 |
| 135 | + |
| 136 | + * **レキシカルスコープ (Lexical Scope):** 関数が「どこで呼び出されたか」ではなく、**「どこで定義されたか」**によってスコープが決まるというルールです。 |
| 137 | + * **スコープチェーン (Scope Chain):** 変数を探す際、現在のスコープになければ、定義時の外側のスコープへと順番に探しに行く仕組みです。 |
| 138 | + |
| 139 | +```js:scope.js |
| 140 | +const globalVar = "Global"; |
| 141 | + |
| 142 | +function outer() { |
| 143 | + const outerVar = "Outer"; |
| 144 | + function inner() { |
| 145 | + const innerVar = "Inner"; |
| 146 | + // innerの中からouterVarとglobalVarが見える(スコープチェーン) |
| 147 | + return `${globalVar} > ${outerVar} > ${innerVar}`; |
| 148 | + } |
| 149 | + return inner(); |
| 150 | +} |
| 151 | + |
| 152 | +console.log(outer()); |
| 153 | +``` |
| 154 | + |
| 155 | +```js-exec:scope.js |
| 156 | +Global > Outer > Inner |
| 157 | +``` |
| 158 | + |
| 159 | +## クロージャ:関数が状態を持つ仕組み |
| 160 | + |
| 161 | +クロージャ (Closure) は、この章の最重要トピックです。 |
| 162 | +一言で言えば、**「外側の関数のスコープにある変数を、外側の関数の実行終了後も参照し続ける関数」**のことです。 |
| 163 | + |
| 164 | +通常、関数(`createCounter`)の実行が終わると、そのローカル変数(`count`)はメモリから破棄されます。しかし、その変数を参照している内部関数(`increment`)が存在し、その内部関数が外部に返された場合、変数は破棄されずに保持され続けます。 |
| 165 | + |
| 166 | +### クロージャの実例:カウンタ |
| 167 | + |
| 168 | +プライベートな変数を持つカウンタを作ってみましょう。 |
| 169 | + |
| 170 | +```js:closure_counter.js |
| 171 | +const createCounter = () => { |
| 172 | + let count = 0; // この変数は外部から直接アクセスできない(プライベート変数的な役割) |
| 173 | + |
| 174 | + return () => { |
| 175 | + count++; |
| 176 | + console.log(`Current count: ${count}`); |
| 177 | + }; |
| 178 | +}; |
| 179 | + |
| 180 | +const counterA = createCounter(); // counterA専用のスコープ(環境)が作られる |
| 181 | +const counterB = createCounter(); // counterB専用のスコープが別に作られる |
| 182 | + |
| 183 | +counterA(); // 1 |
| 184 | +counterA(); // 2 |
| 185 | +counterA(); // 3 |
| 186 | + |
| 187 | +console.log("--- switching to B ---"); |
| 188 | + |
| 189 | +counterB(); // 1 (Aの状態とは独立している) |
| 190 | +``` |
| 191 | + |
| 192 | +```js-exec:closure_counter.js |
| 193 | +Current count: 1 |
| 194 | +Current count: 2 |
| 195 | +Current count: 3 |
| 196 | +--- switching to B --- |
| 197 | +Current count: 1 |
| 198 | +``` |
| 199 | + |
| 200 | +### なぜクロージャを使うのか? |
| 201 | + |
| 202 | +1. **カプセル化 (Encapsulation):** 変数を隠蔽し、特定の関数経由でしか変更できないようにすることで、予期せぬバグを防ぎます。 |
| 203 | +2. **状態の保持:** グローバル変数を使わずに、関数単位で永続的な状態を持てます。 |
| 204 | +3. **関数ファクトリ:** 設定の異なる関数を動的に生成する場合に役立ちます。 |
| 205 | + |
| 206 | +## この章のまとめ |
| 207 | + |
| 208 | + * **関数定義:** 巻き上げが起こる「関数宣言」と、起こらない「関数式(アロー関数含む)」がある。 |
| 209 | + * **アロー関数:** `(args) => body` の形式で記述し、`this` の挙動が従来と異なる。 |
| 210 | + * **引数:** デフォルト引数とRestパラメータ(`...args`)で柔軟な引数処理が可能。 |
| 211 | + * **レキシカルスコープ:** 関数は「定義された場所」のスコープを記憶する。 |
| 212 | + * **クロージャ:** 内部関数が外部関数の変数を参照し続ける仕組み。データの隠蔽や状態保持に使われる。 |
| 213 | + |
| 214 | +## 練習問題1: アロー関数への書き換え |
| 215 | + |
| 216 | +以下の関数宣言を、アロー関数 `isEven` に書き換えてください。ただし、省略可能な記号(カッコやreturnなど)は可能な限り省略して最短で記述してください。 |
| 217 | + |
| 218 | +```js:practice4_1.js |
| 219 | +function isEven(n) { |
| 220 | + return n % 2 === 0; |
| 221 | +} |
| 222 | +``` |
| 223 | + |
| 224 | +```js-exec:practice4_1.js |
| 225 | +``` |
| 226 | + |
| 227 | +### 問題2: クロージャによる掛け算生成器 |
| 228 | + |
| 229 | +`createMultiplier` という関数を作成してください。この関数は数値 `x` を引数に取り、呼び出すたびに「引数を `x` 倍して返す関数」を返します。 |
| 230 | + |
| 231 | +**使用例:** |
| 232 | + |
| 233 | +```js:practice4_2.js |
| 234 | +// ここに関数を作成 |
| 235 | + |
| 236 | + |
| 237 | +const double = createMultiplier(2); |
| 238 | +console.log(double(5)); // 10 |
| 239 | + |
| 240 | +const triple = createMultiplier(3); |
| 241 | +console.log(triple(5)); // 15 |
| 242 | +``` |
| 243 | + |
| 244 | +```js-exec:practice4_2.js |
| 245 | +``` |
0 commit comments