アロー関数は、ECMAScript 2015(ES6)で追加された => を使う関数の書き方です。短く書けるだけではありません。this が呼び出し方ではなく定義した位置で決まり、new できず、arguments も持ちません。この差を知らずに function と入れ替えると、オブジェクトのメソッドが例外も出さずに壊れます。この記事では、Node.js v26.5.0・TypeScript 7.0.2・ESLint 10.14.0・React Compiler 1.0.0 で実際に動かした結果をもとに、違いと使い分けを整理します。PHPの fn と、名前の似た「アロー演算子」との違いも最後に扱います。
まとめ:アロー関数とfunctionの使い分けの結論
実務の原則は1行で済みます。コールバックはアロー関数、this で自分のオブジェクトを触るメソッドと new する関数は function(またはクラス構文)です。map・setTimeout・Promiseの then に渡す処理は、外側の this をそのまま使えるアロー関数の方が安全です。
アロー関数にすると構文エラーになるのは、コンストラクタ(new)とジェネレータ(yield)です。それ以外は書けても意味が変わり、this と arguments が外側のものになります。オブジェクトリテラルのメソッドをアロー関数にすると、this が意図した対象を指さないまま動くので、構文エラーより見つかるのが遅れます。どちらでも書ける場面は、前方から呼びたい関数だけ function 宣言にし、残りはESLintの設定で固定すると、レビューで好みの議論をせずに済みます。Reactのコンポーネントはどちらでも挙動は同じで、TypeScriptの .tsx でジェネリックを取るときだけ function 宣言が有利です。
アロー関数の書き方と省略ルール
基本形は (引数) => 式 です。本体が式1つなら波括弧と return を省略でき、これを式本体と呼びます。引数の丸括弧を省略できるのは、単純な引数がちょうど1つのときだけです。
const double = (n) => n * 2;
const double2 = n => n * 2; // 単純な引数1つなら括弧を省略できる
const greet = () => "hello"; // 引数なしは括弧が必須
const sum = (a, b) => a + b; // 複数の引数も括弧が必須
const first = ([head]) => head; // 分割代入・デフォルト値・残余引数も括弧が必須
const total = (a, b) => { // 複数の文を書くなら波括弧と return
const s = a + b;
return s;
};
書き間違えやすい3つの形と実行結果
次の3つは、エラーにならずに値が変わるか、構文エラーで止まるかのどちらかです。コメントは、各例を1つずつ Node.js v26.5.0 で評価した結果です(まとめて実行すると最初の構文エラーで止まります)。
(() => { name: "x" })(); // undefined(波括弧が関数本体、name: はラベル扱い)
(() => ({ name: "x" }))(); // { name: "x" }(オブジェクトは丸括弧で囲む)
const f = (a)
=> a; // SyntaxError: Unexpected token '=>'
let cb;
cb = cb || () => {}; // SyntaxError: Malformed arrow function parameter list
cb = cb || (() => {}); // 丸括弧で囲めば動く
1つ目は最も気づきにくい形です。戻り値が undefined になるだけで、例外は出ません。2つ目は、引数の括弧と => の間で改行した場合です。=> の後ろなら改行しても問題ありません。3つ目は、MDNのアロー関数式のページが「アロー関数の優先順位」として挙げている例で、|| の右辺に置くときは丸括弧が要ります。
async・即時実行・カリー化の読み方
他人のコードで戸惑いやすい書き方を3つ並べます。
const load = async (url) => (await fetch(url)).json(); // async は引数の前に置く
(() => {
// 即時実行:定義してすぐ呼ぶ。外にスコープを漏らさない
})();
const add = (a) => (b) => (c) => a + b + c; // 関数を返す関数を返す
add(1)(2)(3); // 6
async と引数の括弧の間で改行すると、async が通常の関数呼び出しとして解釈され、Node.js v26.5.0 では SyntaxError: Malformed arrow function parameter list になります。a => b => c => ... の形は、右から function (a) { return function (b) { ... } } と展開して読むと構造が分かります。引数を1つずつ受け取るこの書き方は、関数型プログラミングとは?純粋関数・不変性・高階関数をTypeScriptの実例で解説で高階関数として扱っています。
thisの決まり方:functionは呼び出し方、アロー関数は定義位置
function で定義した関数の this は、定義した場所ではなく呼び出し方で決まります。obj.method() なら obj、単独で fn() なら undefined(strictモード)かグローバルオブジェクト、call・apply で渡せばその値です。アロー関数は自分の this を持たず、定義したスコープの this をそのまま参照します。call・apply・bind で別の値を渡しても無視されます。
オブジェクトのアロー関数プロパティとthisの参照先
const counter = {
count: 0,
incArrow: () => { this.count++; }, // this はオブジェクトの外側
incMethod() { this.count++; }, // this は counter
};
counter.incArrow();
console.log(counter.count); // 0 のまま
this を使わない関数ならアロー関数のプロパティでも問題ありません。this で自分のオブジェクトを触る場合の壊れ方は、実行環境で分かれます。Node.js v26.5.0 の CommonJS では、トップレベルの this が module.exports を指すため、例外が出ないまま count が 0 で残ります。ES Modules ではトップレベルの this が undefined なので、同じコードが TypeError: Cannot read properties of undefined (reading 'count') で止まります。黙って壊れるCommonJSの方が発見は遅れます。
コールバック・イベントリスナーのthis継承と使い分け
同じ性質が、コールバックでは利点になります。function を渡すと this が呼び出し元の都合で差し替わるためです。Node.js v26.5.0 で確かめると、setInterval に渡した function の this は Timeout オブジェクト、EventTarget の addEventListener に渡した function の this はイベントのターゲットでした。アロー関数ならどちらも外側の this のままです。
class Timer {
constructor() { this.sec = 0; }
start() {
setInterval(() => { this.sec++; }, 1000); // this は Timer インスタンス
}
}
button.addEventListener("click", function () {
this.classList.toggle("on"); // this はリスナーを登録した button
});
ES5では var self = this; で退避するのが定番でしたが、アロー関数ならこの行が要りません。逆に、イベントリスナーの中で「リスナーを登録した要素」を this で取りたいときは function を使うか、アロー関数で event.currentTarget を参照します。button の中の子要素をクリックした場合、実際にクリックされた要素は event.target で、this とは一致しません。コールバックとPromiseの関係は非同期処理とは?同期処理との違いから実装方式まで実装者目線で解説で整理しています。
クラスフィールドのアロー関数とメソッドの違い
class C {
field = () => this; // クラスフィールド
method() { return this; }
}
const c1 = new C(), c2 = new C();
c1.field === c2.field; // false(インスタンスごとに別の関数)
c1.method === c2.method; // true(prototype で共有)
const { field, method } = c1;
field() === c1; // true(取り出しても this が外れない)
method(); // undefined(this が外れる)
クラスフィールドのアロー関数は、メソッドを取り出して渡しても this が外れません。そのかわりインスタンスを作るたびに関数が1つ増えます。取り出して渡しても this を保ちたいメソッドだけをクラスフィールドにし、インスタンスを大量に作るクラスでは、メモリ使用量を計測したうえで bind やメソッドと比べて選びます。
アロー関数にできないこと:new・arguments・ジェネレータ・引数名の重複
this 以外の差を Node.js v26.5.0 で確かめた結果です。
| 項目 | function | アロー関数 |
|---|---|---|
| this | 呼び出し方で決まる | 外側を継承 |
| new | 可 | TypeError |
| prototype プロパティ | あり | なし |
| arguments | 自分のもの | 外側の関数のもの |
| yield(ジェネレータ) | 可 | SyntaxError |
| 引数名の重複 | 非strictかつ単純な引数リストなら可 | 常にSyntaxError |
| call・apply・bind の this | 反映される | 無視される |
| super | メソッド構文のみ可(通常の function は不可) | 外側のメソッドを継承 |
| new.target | 自前の束縛 | 外側を継承 |
最も遭遇しやすいのは new で、TypeError: Foo is not a constructor で止まります。arguments は「無い」のではなく、書くと外側の arguments を探しにいきます。外側の関数の中なら、その関数の arguments を黙って参照します。ES Modules のトップレベルのように外側に束縛が無い場所では ReferenceError: arguments is not defined です(CommonJSでは、モジュールを包む関数の arguments が見えてしまいます)。可変長の引数は残余引数 (...args) => で受けてください。引数名の重複は、function (a, a) が非strictモードでは通るのに対し(デフォルト値などを含む function (a, a = 1) は非strictでもSyntaxError)、(a, a) => a は SyntaxError: Duplicate parameter name not allowed in this context になります。
super と new.target は誤解が多い行です。アロー関数は自前の束縛を持たないだけで、外側のメソッドやコンストラクタのものを使えます。クラスのメソッド内で定義したアロー関数から super.hello() を呼ぶと動き、コンストラクタ内のアロー関数から new.target も取れました。super を使えるのはクラスやオブジェクトリテラルのメソッド構文の中だけなので、トップレベルや通常の function の中に置いたアロー関数で書くと SyntaxError: 'super' keyword unexpected here になります。
functionとの使い分け:巻き上げとESLintでのルール化
制約に触れない場面、つまりどちらでも書ける場面の判断材料は、巻き上げの有無と、チームで揺れないルールの2つです。
関数宣言の巻き上げとconstのTDZ
hoisted(); // 動く
function hoisted() {}
notHoisted(); // ReferenceError: Cannot access 'notHoisted' before initialization
const notHoisted = () => {};
function 宣言は巻き上げられるので、定義より前の行から呼べます。ファイルの上に処理の流れを書き、下に部品を並べたいときや、相互再帰のように互いを前方参照するときは function 宣言が向きます。相互再帰は再帰関数とは?自分を呼ぶ関数の仕組みとスタック・末尾再帰・実務判断を解説で扱っています。
「アロー関数は無名なのでスタックトレースで追えない」という説明は今は当てはまりません。const myArrow = () => {} は変数名から名前が推論され、myArrow.name は "myArrow"、Node.js v26.5.0 のスタックトレースにも at myArrow と出ます。名前が出ないのは、map の引数などに直接書いたアロー関数だけです。
ESLint 10で使い分けを固定する設定
// eslint.config.mjs
export default [
{
rules: {
"func-style": ["error", "declaration", { allowArrowFunctions: true }],
"prefer-arrow-callback": "error",
"arrow-body-style": ["error", "as-needed"],
},
},
];
この設定で次の4行を ESLint 10.14.0 にかけると、3行に指摘が出ました。
const a = [1, 2].map(function (n) { return n * 2; }); // prefer-arrow-callback: Unexpected function expression.
const top = () => 1; // 指摘なし(allowArrowFunctions で許可)
const f = function () {}; // func-style: Expected a function declaration.
const b = (x) => { return x + 1; }; // arrow-body-style: Unexpected block statement ...
3つのルールはいずれも非推奨ではありませんが、ESLint 10.14.0 のルール定義では3つとも frozen: true で、ESLint公式のルールページにも Frozen と表示されています。新しいオプションを受け付けないという意味で、動作は維持されるので設定に残して構いません。allowArrowFunctions: true はトップレベルのアロー関数の代入も許すので、関数宣言に統一したいなら外します。
一方、arrow-parens・arrow-spacing・no-confusing-arrow は非推奨で、ルール定義の移行先は @stylistic/eslint-plugin の同名ルールです。古い記事の設定をそのまま貼っている場合は移してください。ルールで決めることとレビューで見ることの分け方はコードレビューとは?目的・観点・進め方と実装現場の運用設計を解説で書いています。チームの規約づくりから開発を任せたい場合は、フルスクラッチ開発でご相談いただけます。
TypeScriptでのアロー関数:型注釈・thisパラメータ・.tsxのジェネリック
TypeScriptでは => が、値としてのアロー関数と、関数の型の両方に出てきます。
const add: (a: number, b: number) => number = (a, b) => a + b;
// ↑ 型(数値2つを受けて数値を返す) ↑ 値(実際の関数)
type ClickHandler = (e: MouseEvent) => void;
function handler(this: HTMLElement) { return this.id; } // this の型を宣言できる
const bad = (this: HTMLElement) => 1; // TS2730
関数の型を型エイリアスにしておくと、コールバックの引数に毎回注釈を書かずに済みます。this の型を宣言する this パラメータは function 専用で、アロー関数に書くと TypeScript 7.0.2 では TS2730: An arrow function cannot have a 'this' parameter. になります。アロー関数は this を外側から受け取るので、宣言する意味がないためです。
厄介なのは .tsx でのジェネリックです。<T> がJSXの開始タグと区別できず、TypeScript 7.0.2 では TS17008: JSX element 'T' has no corresponding closing tag. と TS1382 が出ます(閉じタグを探す副作用で TS1005 も出ます)。
const bad = <T>(x: T): T => x; // .tsx ではエラー
const ok1 = <T,>(x: T): T => x; // 末尾カンマを付ける
const ok2 = <T extends unknown>(x: T): T => x; // 制約を付ける
function ok3<T>(x: T): T { return x; } // function 宣言なら制約なし
下の3行はエラーなしで通ります。<T,> は意図が読み取りにくいので、ジェネリックなヘルパーは function 宣言で書くのが無難です。同じ関数を .ts に置けばこの制約はありません。TypeScriptの版ごとの差はTypeScript 6.0とは|5から6への移行手順とts5to6・7.0との違い【6.0.3対応】にまとめています。
Reactでのアロー関数:function宣言とどっちか、インライン関数とReact Compiler
コンポーネントの定義はアロー関数でよく、ジェネリックを取るコンポーネントだけ function 宣言にします。関数コンポーネントはどちらで書いても挙動もHooksの使い方も同じで、差が出るのは前の章の .tsx の制約だけです。
判断が分かれてきたのは、JSXの属性に直接書くインラインのアロー関数です。
const List = memo(function List({ onSelect }) { ... });
function Counter() {
const [count, setCount] = useState(0);
return <List onSelect={() => setCount(count + 1)} />;
}
コンパイラを使わない場合、この関数はレンダーのたびに作り直され、memo した List も毎回再レンダーされます。そのため useCallback で参照を固定するのが定石でした。ただしこの関数は count を閉じ込めているので、依存配列に count を入れれば count が変わるたびに参照も変わります。固定が効くのは、count が変わらない再レンダーのときです。
React Compilerを入れると前提が変わります。上のコードを babel-plugin-react-compiler 1.0.0 で変換すると、Counter の中身は次のようになりました(抜粋。_jsxDEV の末尾引数は省略)。
const $ = _c(2);
let t0;
if ($[0] !== count) {
t0 = _jsxDEV(List, { onSelect: () => setCount(count + 1) });
$[0] = count;
$[1] = t0;
} else {
t0 = $[1];
}
インラインのアロー関数を含む要素ごと、count が変わらない限り前回の値が再利用されます。手で useCallback を書かなくても、参照が変わるのは count が変わったときだけです。React Compilerを導入済みなら、インラインのアロー関数を理由に書き方を変える必要はありません。未導入のプロジェクトでも、通常のDOM要素に渡すだけなら関数の作り直しは問題になりません。useCallback を検討するのは、React公式のuseCallbackのページが挙げるように、memo した子に渡すときや、Effectなど他のHookの依存配列に入る関数、カスタムHookから返す関数です。導入手順はReact Compilerとは?Vite・Next.js・Expoでの導入とRust版・ESLint・useMemoの扱い【2026年10月】で解説しています。
PHPのアロー関数fnとアロー演算子:JavaScriptとの違い
「アロー関数」で検索すると、PHPの公式マニュアルも上位に出ます。PHPのアロー関数は fn キーワードで書き、PHPマニュアルのアロー関数のページのとおり PHP 7.4 で追加されました。JavaScriptとの違いは、外側の変数の取り込み方と、本体に書ける量です。コメントは PHP 8.5.8 での実行結果で、最後の1行だけは別ファイルで実行した結果です(同じファイルに置くと全体が解析エラーになります)。
<?php
$y = 1;
$fn = fn($x) => $x + $y; // use を書かなくても $y を取り込む
echo $fn(2); // 3
$y = 10;
echo $fn(2); // 3 のまま(定義した時点の値をコピー)
$inc = fn() => $y++;
$inc();
echo $y; // 10 のまま(外側の変数は書き換わらない)
echo implode(",", array_map(fn($v) => $v * 2, [1, 2, 3])); // 2,4,6
$f = fn($x) => { return $x; }; // Parse error: syntax error, unexpected token "{"
JavaScriptのアロー関数は外側の変数を参照で共有するので、後から $y にあたる変数を書き換えると結果も変わります。PHPの fn は定義時の値をコピーするので変わりません。また、PHPの fn の本体は式1つだけで、波括弧のブロックは書けません。複数の文が要るときは従来の function () use ($y) { ... } を使います。$this はクラスのメソッド内なら自動で使え、この点はJavaScriptと同じ発想です。PHPの最新版の変更点はPHP 8.5の変更点と新機能|8.4との違い・非推奨・性能を実行結果で確認【8.5.12対応】で確認できます。
もう1つ紛らわしいのが「アロー演算子」です。これはPHPの $obj->prop やC・C++の ptr->member のように、-> でオブジェクトのメンバーを参照する記号の呼び名です。関数を作る構文ではなく、JavaScriptには -> という演算子はありません。JavaScriptの => もアロー関数の構文の一部で、単独の演算子ではありません。
ラムダ式・無名関数との違い:Java・Python・C#との比較
アロー関数を「JavaScriptのラムダ式」と呼ぶ説明もありますが、仕様上の名前はアロー関数(ECMAScript仕様の Arrow Function Definitions)です。名前を持たない関数を式として書く仕組みは各言語にあり、記号と書ける量が違います。
| 言語 | 記法 | 本体 | 外側の変数 |
|---|---|---|---|
| JavaScript | (a, b) => a + b |
式・複数文 | 参照で共有 |
| PHP | fn($a, $b) => $a + $b |
式のみ | 値をコピー |
| Java | (a, b) -> a + b |
式・複数文 | 実質finalのみ |
| Python | lambda a, b: a + b |
式のみ | 参照で共有 |
| C# | (a, b) => a + b |
式・複数文 | 参照で共有 |
Javaのラムダ式は関数型インターフェースの実装として扱われ、取り込めるローカル変数は再代入しない(実質final)ものに限られます。この制約はキャプチャリングとは?Javaのラムダ式・匿名クラスと実質finalの制約で、使いどころはJavaラムダ式の使いどころ|Stream APIで効く場面と避ける場面をJava 25で実測で詳しく扱っています。
よくある質問
アロー関数とはどういう意味ですか?
ECMAScript 2015(ES6)で追加された、=> を使って関数を式として書く構文です。記号が矢印(arrow)に見えることが名前の由来です。this・arguments・super・new.target を自分では持たず、外側のものを使う点が function との本質的な違いです。
JavaScriptの () と => は何を表しますか?
() が引数のリスト、=> が引数と本体の区切りです。(a, b) => a + b は「a と b を受け取り、a + b を返す関数」と読みます。=> の右に波括弧を書くと通常の関数本体になり、値を返すには return が要ります。
アロー関数のメリットは何ですか?
コールバックの中で外側の this をそのまま使えることです。var self = this; や bind(this) が不要になり、map や then に渡す短い処理を1行で書けます。逆に、コンストラクタには構文上使えず、this で自分のオブジェクトを触るメソッドには向きません。
アロー関数とfunctionはどちらが速いですか?
どちらが速いかを示す公式の基準はなく、速度差は実行環境と処理内容に依存します。選ぶ基準は this の扱いと制約にし、性能が問題になる箇所は対象環境で計測して判断します。メモリで差が出るのは、クラスフィールドにアロー関数を置いてインスタンスを大量に作る場合で、条件は「クラスフィールドのアロー関数とメソッドの違い」に書いています。
アロー関数はどの環境から使えますか?
MDNの互換性情報では、2016年9月以降すべての主要ブラウザーで使えます。現行のブラウザとNode.jsではそのまま動き、変換は要りません。Internet Explorer 11 を対象に含む古い案件だけ、BabelでES5に変換します。