🧭

【JavaScript】functionとアロー関数の違いと使い分け

に公開
4

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」なら関数式

普段何気なく使っている関数ですが、つい忘れがちな違いなので、
迷った際はこのページを見返してみてください。

GitHubで編集を提案

Discussion

junerjuner

関数宣言だけではなくて関数式も比較したらいいかもしれませんね。

div.sawadiv.sawa

コメントありがとうございます!たしかに関数宣言だけでなく、関数式も比較した方が分かりやすいですね。
ご意見を反映して「関数宣言/関数式/アロー」の3つで比較する内容を追記しました。

codebydeercodebydeer

setTimeoutなどの非同期APIやその他コールバック方式APIのコールバックとして「thisがズレる」と言った表現が少し気になりました。
setTimeoutの例で言えば、thisは通常(functionの場合)setTimeoutの内部処理関数のthisになるはずではないでしょうか?(この場合、setTimeout内関数のthisはプライベートになっているためundefind。)
なので、もともとコールバックは呼び出し元のスコープにアクセスできるはずはない(ガベージコレクションのライフタイム的にも)ですよね?
それを、見えない引数としてthisをコールバックに引き渡せるようにしたのがアロー関数ですから、「ズレる」は少し違うのではないかと思いました。
それと正直これを書き分けるくらいなら、thisを使うオブジェクトはclassで書いた方がいい気がしますよね。
関数なら関数、
thisを使うほどのオブジェクトならクラス、コールバックならアロー関数、
その他外部からの呼び出しをした際呼び出し元のthisを使いたい場合(独自定義コールバックなど)はアロー関数
といった使い分けが1番無難そうですね...

div.sawadiv.sawa

コメントありがとうございます!

ご指摘の通り「this がズレる」は少し雑だったので、本文を「期待した this(元のオブジェクト)を指さなくなる」という表現に修正しました。あわせて、理解の入口としてイベントリスナーの例を追加し、function とアローの違いを確認してから setTimeout の例へつなげる構成にしています。

いろいろ調べてみると奥行きのある内容だったので、また別途まとめてみます。ありがとうございました。