Menu
日本語

JavaScriptのthisを完全理解:4つのバインディングルール

JavaScriptのthisが実際にどう動くのか。4つのバインディングルール、アロー関数でthisが違う理由、そして「thisがundefined問題」のハマりどころを整理します。

this は呼び出された瞬間に決まる

this にまつわる混乱のほとんどは、「this は関数が 定義された 場所で決まる」という思い込みから生まれます。実はそうではありません。this の値は、関数が どのように呼ばれたか で決まります。

同じ関数でも、呼び方を変えるとどうなるか見てみましょう。

index.js
Output
Click Run to see the output here.

同じ関数でも、this の中身はこうも変わります。最初の呼び出しではドットの前に user があるので、thisuser になります。一方、2 回目の呼び出しではドットの前に何もないため、thisundefined です。関数自体は一切変えていません。変わったのは「どう呼び出したか」だけです。

ここで覚えておいてほしいポイントは 1 つ。this の正体を知りたければ、関数の定義ではなく呼び出し方を見るということです。

this バインディングの 4 つのルール

this が決まるパターンは全部で 4 つあります。this まわりでつまずいたときは、だいたいこの 4 つのうちどれに当てはまるかを考えれば答えが出ます。

1. メソッド呼び出し: obj.fn()

関数がオブジェクトのプロパティとして呼び出された場合、this はそのオブジェクトになります。

index.js
Output
Click Run to see the output here.

ドットの前にあるものが、そのまま this になります。シンプルですね。

2. 普通の関数呼び出し: fn()

前に何もオブジェクトを付けずに呼び出した場合、strictモードでは thisundefined になります(モジュールやクラスの中では自動的にstrictモードが有効になります)。逆にsloppyモードではグローバルオブジェクトが入ります。

index.js
Output
Click Run to see the output here.

ここで出てくるのが、あの悪名高い「this is undefined」エラーです。オブジェクトからメソッドを取り出した瞬間、メソッド呼び出しはただの関数呼び出しに変わってしまいます。

index.js
Output
Click Run to see the output here.

counter. を付けずに呼び出しているので、バインドもされません。関数は自分がどのオブジェクトから来たかを覚えていないのです。

3. 明示的なバインディング: .call().apply().bind()

this を好きな値に強制的に設定することもできます。

index.js
Output
Click Run to see the output here.

.call.apply はその場で関数を呼び出す点は同じで、違いは引数の渡し方だけです。一方 .bindthis を固定した新しい関数を返すので、コールバックに渡したいときに重宝します。

4. new 呼び出し: new Fn()

関数を new 付きで呼び出すと、新しいオブジェクトが生成され、それが this に束縛されます。

index.js
Output
Click Run to see the output here.

クラスも内部的にこの仕組みを利用しています。詳しくは後の章で取り上げます。

アロー関数には自分の this がない

アロー関数は、ここまで説明してきたルールをあえて無視します。そもそも this をバインドしません。代わりに、アロー関数が定義された時点の外側のスコープの this をそのまま引き継ぎます。

index.js
Output
Click Run to see the output here.

モジュールのトップレベルで定義したアロー関数は、そのモジュールの this、つまり undefined をキャプチャします。user.arrow() のように呼び出しても、アロー関数は頑として this を再バインドしてくれません。

一見バグのように見えますが、これこそがアロー関数の本質です。アロー関数が本領を発揮するのは、外側の this をそのまま引き継ぎたいメソッドの内部です。

index.js
Output
Click Run to see the output here.

setInterval の中のアロー関数は、start から this を引き継ぎます。timer.start() という形で呼ばれているので、this.seconds もちゃんと動くわけです。これがもし普通の function だったら、setInterval が渡してくる独自の this になってしまって動きません。

覚えておきたいルール: メソッドの中のコールバックにはアロー関数、メソッド自体には通常の関数を使う。 これが基本です。

よくあるコールバックの落とし穴

this が壊れるパターンで一番よく見かけるのがこれ。メソッドをコールバックとして渡した瞬間に、バインディングが失われます。

index.js
Output
Click Run to see the output here.

setTimeoutc.increment() のようなメソッド呼び出しではなく、ただの関数呼び出しとして実行してしまいます。これを直す方法は次の3つです。

index.js
Output
Click Run to see the output here.

どの方法でも動きますが、基本的にはアロー関数でラップするのが一番わかりやすいですね。

トップレベルでの this

トップレベルの this が何を指すかは、そのコードがどこで実行されるかによって変わります。

  • ブラウザのスクリプト(モジュールではない場合): thiswindow になります。
  • ESモジュール(最近のバンドルされたコードのほとんどが該当): thisundefined です。
  • Node.js の CommonJS モジュール: thismodule.exports を指します。

環境を問わずグローバルオブジェクトを確実に参照したい場合は、globalThis を使いましょう。

index.js
Output
Click Run to see the output here.

実務ではトップレベルの this に頼らない方が無難です。グローバルオブジェクトが本当に必要な場面では globalThis を使い、それ以外は値を明示的に渡していきましょう。

this の挙動を判定するフロー

this が何を指すのか分からなくなったら、次の順番で上から確認していけば答えが出ます。

  1. その関数はアロー関数か? そうなら this は外側のスコープのものをそのまま引き継ぎます。呼び出し側がどうであっても関係ありません。
  2. new 付きで呼ばれたか? そうなら this は新しく作られたオブジェクトです。
  3. .call.apply・bind された関数として呼ばれたか? そうなら渡された値が this になります。
  4. obj.method() の形で呼ばれたか? そうなら thisobj です。
  5. ただの fn() として呼ばれたか? strictモードでは thisundefined になります。

この順に上から当てはめていけば、どんなケースでも決着がつきます。

次は高階関数へ

this の正体がはっきりしたところで、いよいよ JavaScript らしさの本丸、関数を値として持ち回すパターンに進みましょう。次回は高階関数 (higher-order functions) ――つまり関数を引数に取ったり関数を返したりする関数――を取り上げます。配列メソッドやイベントハンドラ、そして実際の JavaScript コードの大半を支えているのが、まさにこの仕組みです。

よくある質問

JavaScriptのthisは何を指しているの?

thisが指すのは「その関数がどのオブジェクトから呼ばれたか」であって、どこで定義されたかではありません。たとえばuser.greet()と呼べばthisuserになります。一方、ただのgreet()として呼ぶと、strictモードではundefined、sloppyモードではグローバルオブジェクトになります。ポイントは定義場所ではなく、**呼び出し場所(call site)**だということです。

関数の中でthisundefinedになるのはなぜ?

よくある原因は、メソッドをオブジェクトから外して単独で呼び出してしまうパターン、もしくはコールバックとして渡してしまうパターンです。const fn = user.greet; fn();のように書くと、呼び出し時にドットの左側にオブジェクトが無くなるので、バインディングが失われてしまいます。対処法は.bind(user)を使う、アロー関数でラップする、あるいは素直にuser.greet()として呼ぶ、のいずれかです。

アロー関数のthisは何が違うの?

アロー関数は自分自身のthisを持ちません。定義された時点の外側のスコープのthisをそのまま引き継ぎます。そのため、メソッド内部のコールバックで「外側のthisをそのまま使いたい」ときにとても便利です。逆に言うと、.call().apply().bind()を使ってもアロー関数のthisを上書きすることはできません。

スクリプトのトップレベルでのthisはどうなるの?

ブラウザの通常スクリプトでは、トップレベルのthiswindowオブジェクトです。ESモジュール内ではundefinedになり、Node.jsのCommonJSモジュールではmodule.exportsを指します。環境に依存せず必ずグローバルオブジェクトを参照したい場合は、globalThisを使うのが確実です。

Coddyでコードを学ぼう

始める