avatar

Tetsuya Ohira's Blog

Software Developer in Japan 🚀

JavaScriptのfunction宣言とアロー関数、どう使い分ける?

2025-08-12

アロー関数はかっこいいから全部アロー関数にしがちだが...

ES6でアロー関数が導入されてから、モダンなJavaScriptコードはアロー関数だらけになった。確かに=>という記法はスマートで、コードがモダンに見える。しかし、すべてをアロー関数で書くのが本当にベストプラクティスなのだろうか?

実は、従来のfunction宣言の方が適している場面も多い。今回は両者の違いと適切な使い分けについて整理してみた。

主な違い

1. ホイスティング(巻き上げ)

最も重要な違いの一つがホイスティングである。

// function宣言:定義前でも呼び出せる console.log(add(2, 3)); // 5 function add(a, b) { return a + b; }
// アロー関数:定義前に呼び出すとエラー console.log(add(2, 3)); // ReferenceError: Cannot access 'add' before initialization const add = (a, b) => { return a + b; };

関数宣言はコード実行前に巻き上げられるため、定義の位置を気にせず使える。一方、アロー関数は変数と同じ扱いなので、定義前に使うことはできない。

2. thisの扱い

thisの挙動は両者で大きく異なる。

const obj = { name: 'Object', // function宣言:呼び出し元のthisを参照 methodFunction: function() { console.log(this.name); // 'Object' }, // アロー関数:レキシカルスコープのthisを参照 methodArrow: () => { console.log(this.name); // undefined(グローバルのthis) } };

アロー関数はthisを束縛しない。定義時のスコープのthisを継承する。これはイベントハンドラやコールバックで便利だが、メソッドとして使うと予期しない動作になることがある。

3. argumentsオブジェクト

// function宣言:argumentsが使える function sum() { return Array.from(arguments).reduce((a, b) => a + b, 0); } console.log(sum(1, 2, 3, 4)); // 10 // アロー関数:argumentsは使えない const sumArrow = () => { return Array.from(arguments); // ReferenceError: arguments is not defined };

アロー関数ではargumentsオブジェクトが使えない。代わりにレスト構文(...args)を使う必要がある。

4. コンストラクタとしての使用

// function宣言:コンストラクタとして使える function Person(name) { this.name = name; } const person = new Person('Alice'); // OK // アロー関数:コンストラクタとして使えない const PersonArrow = (name) => { this.name = name; }; const person2 = new PersonArrow('Bob'); // TypeError: PersonArrow is not a constructor

アロー関数はnew演算子と一緒に使えない。

使い分けのガイドライン

function宣言を使うべき場面

1. トップレベルの関数・ユーティリティ関数

// ヘルパー関数は明確にfunctionで定義 function calculateTax(price, rate) { return price * rate; } function validateEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); } function formatDate(date) { return date.toISOString().split('T')[0]; }

独立した処理を行う関数はfunction宣言の方が意図が明確である。

2. 再帰関数

// 名前付き関数として自己参照しやすい function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1); }

3. オブジェクトのメソッド(thisを使う場合)

const counter = { count: 0, increment: function() { this.count++; } };

アロー関数を使うべき場面

1. コールバック関数

// 配列メソッドのコールバック const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2); // イベントリスナー button.addEventListener('click', () => { console.log('Clicked!'); }); // Promise処理 fetch('/api/data') .then(response => response.json()) .then(data => console.log(data));

2. 高階関数の戻り値

const createMultiplier = (factor) => { return (number) => number * factor; }; const double = createMultiplier(2); console.log(double(5)); // 10

3. React Hooks

const useCounter = () => { const [count, setCount] = useState(0); const increment = () => setCount(prev => prev + 1); const decrement = () => setCount(prev => prev - 1); return { count, increment, decrement }; };

4. thisを固定したいイベントハンドラ

class Button { constructor() { this.clicked = false; // アロー関数でthisを固定 this.handleClick = () => { this.clicked = true; }; } }

実践的な例:Server Actionでの使い分け

Next.jsのServer Actionで実際に使い分けた例を見てみる。

'use server'; // メインのエクスポート関数はアロー関数(モダンな慣習) export const fetchPlateauCities = async () => { try { const response = await fetch(API_URL); const data = await response.json(); // データ処理 const cities = data.results .filter(isValidCity) .map(extractCityInfo); return { success: true, cities }; } catch (error) { return { success: false, error: error.message }; } }; // ヘルパー関数はfunction宣言(意図を明確に) function isValidCity(dataset) { return dataset.name.includes('plateau-') && dataset.name.match(/plateau-\d{5}-/); } function extractCityInfo(dataset) { const coordinates = extractCoordinates(dataset); return { id: dataset.id, name: dataset.name, coordinates }; } // プライベートなユーティリティ関数 function extractCoordinates(dataset) { if (!dataset.spatial) return null; const spatial = JSON.parse(dataset.spatial); // 座標計算処理... return { latitude, longitude }; }

スタックトレースでの見え方

デバッグ時の違いも重要である。

// function宣言:関数名が表示される function processData() { throw new Error('Something went wrong'); } // Error: Something went wrong // at processData (file.js:2:9) // アロー関数:変数名が表示される(現代のエンジンでは) const processData = () => { throw new Error('Something went wrong'); }; // Error: Something went wrong // at processData (file.js:2:9) // 無名のアロー関数:匿名として表示される [1, 2, 3].map(() => { throw new Error('Something went wrong'); }); // Error: Something went wrong // at <anonymous> (file.js:2:9)

注意: 現代のJavaScriptエンジン(V8、SpiderMonkey等)では、変数に代入されたアロー関数でも変数名がスタックトレースに表示されることが多い。ただし、無名のアロー関数(コールバック等)では依然として<anonymous>と表示される場合がある。

パフォーマンスの違い

実行速度に大きな違いはないが、メモリ使用量には若干の差がある。

// function宣言:プロトタイプメソッドとして共有可能 function MyClass() {} MyClass.prototype.method = function() {}; // アロー関数:インスタンスごとに作成される class MyClass { method = () => {}; // 各インスタンスで新しい関数が作られる }

大量のインスタンスを作る場合は、この違いが影響することもある。

まとめ

アロー関数は確かにモダンで簡潔だが、すべてをアロー関数で書くのは適切ではない。

使い分けの基本原則:

  • 独立した処理function宣言
  • コンテキストに依存する処理 → アロー関数

コードは動作するだけでなく、意図が伝わることが重要である。function宣言を見れば「これは独立したユーティリティ関数だ」と分かり、アロー関数を見れば「これはコールバックやイベントハンドラだ」と理解できる。

モダンなコードを書くことは大切だが、適材適所で使い分けることで、より読みやすく保守しやすいコードになる。かっこよさだけでなく、実用性も考慮した選択をしていきたい。

参考資料