JavaScriptの基礎 - アロー関数
概要
アロー関数 (Arrow Function) は、ES2015 (ES6)で導入された関数定義の新しい構文である。
=> 記号を使った簡潔な記法により、従来の function キーワードによる関数式をより短く記述できる。
アロー関数の最大の特徴は、自身の this を持たない点である。
通常の関数は呼び出し方によって this の値が変化するが、アロー関数は定義された場所 (レキシカルスコープ) の this を継承する。
この特性により、コールバック関数内で this が予期せず変わるという従来の問題を自然に解決できる。
また、単一の式を返す場合は中括弧とreturnキーワードを省略できる 暗黙return (implicit return) の機能も持つ。
これにより、map や filter 等の配列メソッドと組み合わせた時に簡潔なコードを記述することができる。
一方で、アロー関数は全ての場面で通常の関数の代替として使えるわけではない。
オブジェクトメソッドの定義、コンストラクタとしての使用、arguments オブジェクトの参照が必要な場面ではアロー関数を使用できない。
ReactをはじめとするモダンなJavaScriptフレームワークでは、コンポーネント定義、イベントハンドラ、配列メソッドとの組み合わせにおいてアロー関数が最頻出の関数定義形式となっている。
アロー関数の構文、制約、通常関数との使い分けを正確に理解することは、現代的なJavaScript開発において必須の知識である。
基本構文
基本的な記法
アロー関数の基本的な構文はブロック構文であり、通常の関数式に相当する。
中括弧内に処理を記述し、値を返す場合は明示的にreturnを記述する。
// 基本的なブロック構文
(<引数1>, <引数2>) => {
// 処理
return <戻り値>;
}
通常の関数式とアロー関数の対比を以下に示す。
// 通常の関数式
const add = function(a, b) {
return a + b;
};
// アロー関数 (ブロック構文)
const addArrow = (a, b) => {
return a + b;
};
console.log(add(3, 5)); // 8
console.log(addArrow(3, 5)); // 8
複数の処理が必要な場合は、ブロック構文を使用する。
const greet = (name) => {
const message = "こんにちは、" + name + "さん";
console.log(message);
return message;
};
greet("Alice"); // "こんにちは、Aliceさん"
引数の省略記法
アロー関数では、引数の数に応じて括弧を省略できる場合がある。
引数が1つの場合、括弧を省略することができる。
// 引数が1つ: () を省略可能
const double = x => {
return x * 2;
};
// 括弧あり (どちらでも動作する)
const doubleWithParens = (x) => {
return x * 2;
};
console.log(double(5)); // 10
console.log(doubleWithParens(5)); // 10
引数が0個の場合、括弧は省略できず必須である。
// 引数が0個: () は必須
const sayHello = () => {
return "Hello!";
};
console.log(sayHello()); // "Hello!"
引数が複数の場合、括弧は必須である。
// 引数が複数: () は必須
const multiply = (a, b) => {
return a * b;
};
const sum3 = (a, b, c) => {
return a + b + c;
};
console.log(multiply(4, 5)); // 20
console.log(sum3(1, 2, 3)); // 6
省略記法
単一式の暗黙return
アロー関数では、関数本体が単一の式である場合に中括弧とreturnキーワードを省略できる。
式の評価結果が自動的に戻り値となる。
これを、暗黙return (implicit return) と呼ぶ。
// ブロック構文 (明示的 return)
const add = (a, b) => {
return a + b;
};
// 式本体 (暗黙 return)
const addShort = (a, b) => a + b;
console.log(add(3, 4)); // 7
console.log(addShort(3, 4)); // 7
暗黙return が使えるのは式のみであり、if 文や for 文等の文は使用できない。
// 式: 暗黙returnが使用できる
const isEven = n => n % 2 === 0;
const getName = user => user.name;
console.log(isEven(4)); // true
console.log(isEven(3)); // false
配列の map や filter との組み合わせで、暗黙return の効果が際立つ。
const numbers = [1, 2, 3, 4, 5];
// 通常の関数式
const doubled1 = numbers.map(function(n) {
return n * 2;
});
// アロー関数 (暗黙 return)
const doubled2 = numbers.map(n => n * 2);
// filter との組み合わせ
const evens = numbers.filter(n => n % 2 === 0);
console.log(doubled2); // [2, 4, 6, 8, 10]
console.log(evens); // [2, 4]
オブジェクトリテラルの返却
アロー関数で 暗黙return を使用してオブジェクトリテラルを返す場合は、括弧で囲む必要がある。
中括弧 {} はブロックとして解釈されるため、そのまま記述するとエラーとなる。
// 正しい書き方: () で囲んでオブジェクトリテラルとして扱う
const getUser2 = (name, age) => ({ name: name, age: age });
// 誤った書き方: {} がブロックと解釈され、undefined が返る
const getUser1 = (name, age) => { name: name, age: age };
// SyntaxError または undefined
// プロパティ短縮記法との組み合わせ
const getUser3 = (name, age) => ({ name, age });
console.log(getUser2("Alice", 30)); // { name: "Alice", age: 30 }
console.log(getUser3("Bob", 25)); // { name: "Bob", age: 25 }
配列メソッドでオブジェクトを返す時の例を以下に示す。
const users = ["Alice", "Bob", "Charlie"];
// 正: () で囲む
const result2 = users.map(name => ({ id: name.length, name: name }));
// 誤: {} がブロックとして解釈される
// const result1 = users.map(name => { id: name.length, name: name });
console.log(result2);
// [
// { id: 5, name: "Alice" },
// { id: 3, name: "Bob" },
// { id: 7, name: "Charlie" }
// ]
省略記法の使い分け
ブロック構文と式本体のどちらを使うかは、処理の内容と可読性を基準に判断する。
| 構文 | 使用場面 |
|---|---|
| 式本体 (暗黙 return) | 単一の式を評価して返すだけの処理map、filter、find 等の配列メソッドのコールバックシンプルな変換処理や条件式 |
| ブロック構文 | 複数の処理ステップが必要な場合 変数への代入や条件分岐が必要な場合 console.log 等の副作用を伴う処理
|
// 式本体が適切な場面 (シンプルな変換)
const prices = [100, 200, 300];
const taxIncluded = prices.map(price => price * 1.1);
const expensive = prices.filter(price => price >= 200);
// ブロック構文が適切な場面 (複数の処理)
const formatPrice = (price) => {
const taxed = Math.floor(price * 1.1);
return taxed.toLocaleString() + "円";
};
console.log(taxIncluded); // [110, 220, 330]
console.log(formatPrice(1000)); // "1,100円"
thisを束縛しない特性
レキシカルthis
アロー関数は自身の this を作成しない。
代わりに、アロー関数が定義された場所 (レキシカルスコープ) の this を継承して使用する。
この特性を、レキシカルthisと呼ぶ。
const obj = {
name: "Alice",
// 通常のメソッド: thisはobjを参照
greet: function() {
console.log("Hello, " + this.name); // "Hello, Alice"
},
// アロー関数はレキシカルthisを継承する
// (このケースでは外側のthisを参照)
};
アロー関数内の this は、関数が定義された時点で確定し、呼び出し方によって変化しない。
通常関数でのthis問題
通常の関数では、this の値は呼び出し方によって動的に決まる。
メソッド内でコールバック関数や setTimeout を使用する時に、this が予期せずグローバルオブジェクト (または、undefined) になるという問題が発生する。
function Timer() {
this.count = 0;
// 問題のあるコード: 通常関数のコールバック
setTimeout(function() {
this.count++; // this は window または undefined
console.log(this.count); // NaN または TypeError
}, 300);
}
const timer = new Timer();
ES2015以前は、この問題を解決するために以下の2つのパターンが広く使用されていた。
// パターン1: self = this パターン
function Timer1() {
this.count = 0;
const self = this; // thisを変数に保存
setTimeout(function() {
self.count++; // selfを通じてアクセス
console.log(self.count); // 1
}, 300);
}
// パターン2: .bind(this) メソッド
function Timer2() {
this.count = 0;
setTimeout(function() {
this.count++;
console.log(this.count); // 1
}.bind(this), 300); // thisをバインド
}
アロー関数による解決
アロー関数を使用すると、this の問題を自然かつ簡潔に解決できる。
アロー関数は外側の this (この場合、Timer インスタンス) を継承するため、追加の工夫が不要である。
function Timer() {
this.count = 0;
// アロー関数: 外側の this を継承する
setTimeout(() => {
this.count++; // this は Timer インスタンスを参照
console.log(this.count); // 1
}, 300);
}
const timer = new Timer();
メソッド内のコールバックでも同様に this が保持される。
const counter = {
count: 0,
items: [1, 2, 3],
// アロー関数コールバック: thisがcounterを参照
increment: function() {
this.items.forEach(item => {
this.count += item; // this は counter を参照
});
console.log(this.count); // 6
}
};
counter.increment();
アロー関数の制約
使用できない場面
アロー関数には、通常の関数と異なる制約があり、以下の場面では使用できない または 使用すべきでない。
| 制約事項 | 説明 |
|---|---|
| オブジェクトメソッドの定義 | アロー関数の this はオブジェクト自身を参照しない。メソッドには通常の関数定義またはメソッド短縮記法を使用する。 |
| コンストラクタとしての使用 | new キーワードでアロー関数を呼び出すと TypeError が発生する。
|
| argumentsオブジェクトの参照 | アロー関数は arguments オブジェクトを持たない。可変長引数が必要な場合は残余引数 (...args) を使用する。
|
| 動的thisが必要な場面 | addEventListener 等でコールバックの this をイベントターゲットにする場合はアロー関数は不適切
|
各制約のコード例
オブジェクトメソッドでの制約を以下に示す。
const person = {
name: "Alice",
// 正: 通常のメソッド定義
greetFunction: function() {
console.log("Hello, " + this.name); // "Hello, Alice"
},
// 正: メソッド短縮記法 (推奨)
greetShorthand() {
console.log("Hello, " + this.name); // "Hello, Alice"
}
// 誤: アロー関数のthisはグローバルを参照
greetArrow: () => {
console.log("Hello, " + this.name); // undefined (グローバル)
},
};
person.greetArrow(); // "Hello, undefined"
person.greetFunction(); // "Hello, Alice"
person.greetShorthand(); // "Hello, Alice"
コンストラクタとしての制約を以下に示す。
// 通常の関数: コンストラクタとして使用できる
function RegularFunction(name) {
this.name = name;
}
const obj1 = new RegularFunction("Alice"); // OK
// アロー関数: コンストラクタとして使用できない
const ArrowFunction = (name) => {
this.name = name;
};
const obj2 = new ArrowFunction("Bob"); // TypeError: ArrowFunction is not a constructor
arguments オブジェクトの制約を以下に示す。
// 通常の関数: arguments オブジェクトを持つ
function regularSum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
// アロー関数: arguments は使用できない
const arrowSum = () => {
// console.log(arguments); // ReferenceError: arguments is not defined
};
// アロー関数での代替: 残余引数を使用
const arrowSumFixed = (...args) => {
return args.reduce((total, n) => total + n, 0);
};
console.log(regularSum(1, 2, 3)); // 6
console.log(arrowSumFixed(1, 2, 3)); // 6
addEventListener での制約を以下に示す。
const button = document.querySelector("button");
// 正: 通常の関数でthisをイベントターゲットとして使用
button.addEventListener("click", function() {
console.log(this); // ボタン要素 (押下された要素)
this.classList.add("clicked"); // OK
});
// 誤: アロー関数のthisは外側のスコープを参照
button.addEventListener("click", () => {
console.log(this); // window (外側のthis)
// this.classList.add("clicked"); // エラーの可能性
});
通常関数との比較
下表に、アロー関数と通常関数の主な違いを示す。
| 特性 | アロー関数 | 通常関数 |
|---|---|---|
| this | レキシカル (定義場所のthisを継承) | 呼び出し方に依存して動的に変化 |
| arguments | なし (残余引数 (...args) を使用) |
あり |
| new可否 | 不可 (TypeError) | 可能 |
| ホイスティング | なし (変数のルールに従う) | 関数宣言のみあり |
| prototype | なし | あり |
| 構文 | () => {} |
function() {}
|
| メソッド定義 | 非推奨 (thisの問題) | 推奨 |
Reactでの使用場面
コンポーネント定義
Reactでは、関数コンポーネントをアロー関数で定義することが一般的なパターンである。
import React, { useState } from "react";
// アロー関数によるコンポーネント定義
const App = () => {
return <h1>Hello, World!</h1>;
};
// propsを受け取るコンポーネント
const Greeting = ({ name }) => {
return <h2>こんにちは、{name}さん</h2>;
};
// 暗黙returnを使用した簡潔な記法 (JSXを括弧で囲む)
const SimpleButton = ({ label }) => (
<button>{label}</button>
);
export default App;
イベントハンドラ
Reactのイベントハンドラとして、アロー関数はインラインで記述することが多い。
アロー関数を使用することにより、外側の this (クラスコンポーネントの場合) や スコープ内の変数に自然にアクセスできる。
import React, { useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
// アロー関数でイベントハンドラを定義
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<p>カウント: {count}</p>
{/* インラインのアロー関数 */}
<button onClick={() => setCount(count + 1)}>+1</button>
{/* ハンドラ関数を渡す (関数を渡すのであって、呼び出さない) */}
<button onClick={handleIncrement}>+1 (ハンドラ使用)</button>
{/* 誤: () をつけると即時呼び出しになる */}
{/* <button onClick={handleIncrement()}>+1</button> */}
</div>
);
};
イベントハンドラでは、関数を渡す時に () を不可しないことが重要である。
onClick={handleClick} は関数の参照を渡すが、onClick={handleClick()} はレンダリング時に即座に関数を呼び出してしまう。
配列メソッドとの組み合わせ
Reactでデータをリストとして表示する時、map と アロー関数の組み合わせが頻繁に使用される。
import React from "react";
const ItemList = ({ items }) => {
return (
<ul>
{/* map とアロー関数でリスト要素を生成 */}
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};
const FilteredList = ({ items }) => {
// filter と map を組み合わせてアクティブな項目のみ表示
const activeItems = items
.filter(item => item.isActive)
.map(item => (
<li key={item.id}>{item.name}</li>
));
return <ul>{activeItems}</ul>;
};
// 使用例
const sampleItems = [
{ id: 1, name: "リンゴ", isActive: true },
{ id: 2, name: "バナナ", isActive: false },
{ id: 3, name: "チェリー", isActive: true }
];
関連情報
- JavaScriptの基礎 - 関数宣言と関数式
- 関数宣言と関数式、デフォルト引数、残余引数
- JavaScriptの基礎 - 変数宣言
- let / const / varの宣言方法、スコープ、ホイスティング
- JavaScriptの基礎 - クロージャ
- レキシカルスコープ、クロージャの仕組みと実用例
- JavaScriptの基礎 - コールバック関数
- コールバック関数、高階関数、タイマ、イベントリスナー