【JavaScript】functionとアロー関数の違いと使い分け
JavaScriptには関数の書き方がいくつかありますが、
実務でよく出てくるのが function(関数宣言) と アロー関数。
見た目だけでなく、挙動が違うポイントがあるので、まとめてみました。
1. 書き方の違い(見た目)
function(関数宣言)
function add(a, b) {
return a + b;
}
関数式
const add = function (a, b) {
return a + b;
};
- 関数を変数に代入する書き方
- this / arguments / new の性質は function(関数宣言)と同じ
- hoisting(巻き上げ)は関数宣言と同じではなく、変数のルールに従う
- 「必要な場所で定義する」「差し替える」など、変数っぽく扱えて便利
アロー関数
const add = (a, b) => a + b;
- アロー関数は短く書ける(特に1行の処理)
- return を省略できるケースがある(式をそのまま返すとき)
2. 最大の違い:this の挙動
実務で困るのはここがほとんどです。
クリックイベントや setTimeout など、コールバックが絡む場面で「思っていた this と違う!」が起きやすいからです。
ポイントはこれだけ:
- 通常の function:this は 呼び出し方(実行時) で決まる
- アロー関数:this は 外側の this を引き継いで固定される(自分の this を持たない)
まずはイベントリスナーで「this がどう決まるか」を体感する
イベントリスナーの function は、this が イベントを受け取った要素になります。
✅ function:this はイベントターゲット要素になる
const btn = document.querySelector("#btn");
btn.addEventListener("click", function () {
console.log(this); // btn(イベントを受け取った要素)
this.classList.add("active");
});
❌ アロー:this は要素にならない(外側の this のまま)
const btn = document.querySelector("#btn");
btn.addEventListener("click", () => {
console.log(this); // アローは外側の this を使う(module/strict では undefined になりやすい)
// this.classList.add("active"); // できない
// アロー関数は自分の this を持たず、
// この関数が書かれている場所(トップレベル)の this を引き継ぐ
// module(自動的に strict mode)ならトップレベルの this は undefined
// 通常のスクリプトなら window
});
✅ アローで要素を扱いたいなら event.currentTarget が王道
const btn = document.querySelector("#btn");
btn.addEventListener("click", (e) => {
e.currentTarget.classList.add("active"); // btn
});
function(関数宣言/関数式):this は「呼び出し方」で決まる
✅ メソッドとして呼ぶと this === obj
const obj = {
name: "Taro",
hello: function () {
console.log(this.name); // this は obj を指す
},
};
obj.hello(); // "Taro"
❌ コールバックとして呼ばれると、this が counter を指さない
const counter = {
count: 0,
start: function () {
setTimeout(function () {
// ここは「ただの関数呼び出し」になり、this は counter を指さない
// モジュール(自動的に strict mode)ならトップレベルの this は undefined
// 通常のスクリプトなら window
this.count++;
console.log(this.count);
}, 500);
},
};
counter.start(); // うまく動かない(strict mode ならエラー)
アロー:this を「外側の this から引き継ぐ(固定される)」
アローは this を自分で持たず、外側の this をそのまま使います。
なので、メソッド内のコールバックをアローにすると安定します。
✅ コールバックをアローにすると外側の this を使える
const counter = {
count: 0,
start: function () {
setTimeout(() => {
// アローは start の this を引き継ぐ(= counter)
this.count++;
console.log(this.count);
}, 500);
},
};
counter.start(); // 1(期待通り)
- start はメソッドとして呼ばれるので counter.start() の時点で this === counter
- コールバックをアローにすると、その this を 引き継いで固定できる
class でも同じ注意が必要
クラスのメソッドも同様に、メソッド参照をそのまま渡すと this が外れます。
❌ メソッドをそのまま渡すと this が外れる
class Counter {
count = 0;
start() {
setTimeout(this.increment, 500);
}
increment() {
this.count++; // module/strict では undefined になりやすい
console.log(this.count);
}
}
const c = new Counter();
c.start(); // エラー
✅ アローでラップするか
class Counter {
count = 0;
start() {
setTimeout(() => this.increment(), 500);
}
increment() {
this.count++;
console.log(this.count);
}
}
✅ クラスフィールドでアロー関数を使う
class Counter {
count = 0;
start() {
setTimeout(this.increment, 500);
}
increment = () => {
this.count++;
console.log(this.count);
};
}
注意:オブジェクトのメソッドをアローで書くとハマる
const obj = {
name: "Taro",
hello: () => {
console.log(this.name);
},
};
obj.hello(); // 多くの場合 undefined(obj を指さない)
アロー関数は 自分の this を持たないので、obj の中に書いても obj を指すようにはならない。
「オブジェクトの中に書いたから this がそのオブジェクトになる」わけではない、がポイントです(アローは外側の this を見るだけ)。
3. arguments が使えるか
arguments は 「渡された引数一覧っぽいオブジェクト」 で、「引数の数が決まっていない(可変長引数)関数で、渡された全部の引数をまとめて扱いたい時」に使われていました。
ただし今の実務だと、同じ目的なら rest引数(…args)を使う方が主流です(読みやすくて、配列として扱えるから)。
function(関数宣言/関数式):arguments がある
function showArgs() {
console.log(arguments);
}
showArgs(1, 2, 3);
↓ 表示される結果
- Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
- もしくは Arguments(3) {0: 1, 1: 2, 2: 3}
※ 表示のされ方はブラウザやDevToolsによって少し違います。
アロー:arguments はない
const showArgs = () => {
console.log(arguments); // ReferenceError になりがち
};
アローで「可変長引数」を扱うなら rest引数 を使うのが基本です。
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
sum(1, 2, 3); // 6
4. new できるか(コンストラクタ)
function(関数宣言/関数式):new できる
function User(name) {
this.name = name;
}
const u = new User("Taro");
アロー:new できない
const User = (name) => {
this.name = name;
};
// new User("Taro"); // TypeError
アロー関数は コンストラクタとして使うための仕組み(prototype など)を持たないため、new の対象になれません。
5. hoisting(巻き上げ)
function(関数宣言):呼び出しが先でもOK
hello();
function hello() {
console.log("hello");
}
関数式 / アロー(= 変数に代入):基本は後から
hello2(); // ReferenceError になりがち
const hello2 = function () {
console.log("hello2");
};
hello3(); // ReferenceError になりがち
const hello3 = () => console.log("hello3");
6. 実務での使い分け(結論)
アロー関数が向いてる場面
- map / filter / forEach などの コールバック
- this を使わない 小さな関数
- 関数を短く書きたいとき
const names = users.map((u) => u.name);
function が向いてる場面
- オブジェクトのメソッド(特に this を使う)
- コンストラクタ(new 前提)
- 先に呼びたい(巻き上げを活かす) → 関数宣言
- 「変数として差し替えたい / 渡したい」 → 関数式
7. 迷ったらこのルールでOK
- 基本はアロー(短く書けて読みやすい)
- ただし 「this を使うメソッド」だけ function
- そして 「メソッド内のコールバック」はアロー が便利(this を引き継げる)
- 「先に呼びたい」なら関数宣言
- 「変数として扱いたい function」なら関数式
普段何気なく使っている関数ですが、つい忘れがちな違いなので、
迷った際はこのページを見返してみてください。
Discussion
関数宣言だけではなくて関数式も比較したらいいかもしれませんね。
コメントありがとうございます!たしかに関数宣言だけでなく、関数式も比較した方が分かりやすいですね。
ご意見を反映して「関数宣言/関数式/アロー」の3つで比較する内容を追記しました。
setTimeoutなどの非同期APIやその他コールバック方式APIのコールバックとして「thisがズレる」と言った表現が少し気になりました。
setTimeoutの例で言えば、thisは通常(functionの場合)setTimeoutの内部処理関数のthisになるはずではないでしょうか?(この場合、setTimeout内関数のthisはプライベートになっているためundefind。)
なので、もともとコールバックは呼び出し元のスコープにアクセスできるはずはない(ガベージコレクションのライフタイム的にも)ですよね?
それを、見えない引数としてthisをコールバックに引き渡せるようにしたのがアロー関数ですから、「ズレる」は少し違うのではないかと思いました。
それと正直これを書き分けるくらいなら、thisを使うオブジェクトはclassで書いた方がいい気がしますよね。
関数なら関数、
thisを使うほどのオブジェクトならクラス、コールバックならアロー関数、
その他外部からの呼び出しをした際呼び出し元のthisを使いたい場合(独自定義コールバックなど)はアロー関数
といった使い分けが1番無難そうですね...
コメントありがとうございます!
ご指摘の通り「this がズレる」は少し雑だったので、本文を「期待した this(元のオブジェクト)を指さなくなる」という表現に修正しました。あわせて、理解の入口としてイベントリスナーの例を追加し、function とアローの違いを確認してから setTimeout の例へつなげる構成にしています。
いろいろ調べてみると奥行きのある内容だったので、また別途まとめてみます。ありがとうございました。