なぜ this は呼び方で変わるのか

this は書いた場所ではなく、呼ばれ方で決まります。メソッドから取り出すと外れる理由、 strict モードでの違い、アロー関数だけが例外である仕組みを、実行して確かめます。

実行して検証済み処理系Node 22.22.3対応ES1難易度●●●○○目安9更新

this は、書いた場所ではなく呼ばれ方で決まるからです。

obj.method() と書けば thisobj になります。 関数を取り出して fn() と呼べば、obj はもうどこにもありません。 関数は自分がどこに書かれていたかを覚えていません。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
あ undefined
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
true

同じ関数です。 呼び方だけを変えたら、結果が変わりました。

何が渡されているのか

user.greet() という式は、2つのことを同時にしています。

  1. user から greet という関数を取り出す
  2. userthis として渡しながら呼ぶ

const fn = user.greet1だけをします。user は捨てられます。 だから fn() には渡すものが無くなります。

呼び方 this
user.greet() user
fn() 無い(下で説明)
greet.call(other) other
new Thing() 新しく作られたもの

「無い」ときに何になるか

ここが分かりにくいところです。モードによって違います。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
undefined
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
TypeError: Cannot read properties of undefined (reading 'n')

渡すものが無いと、グローバルオブジェクトが入ります。 globalThis.name は無いので、最初の例では undefined が出ていました。

'use strict' を付けると、代わりに undefined が入ります。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
3
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
あ object

こちらのほうが親切です。 次に this.name を読もうとした時点で落ちるからです。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
い い い い
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
object

forEach() に渡した関数は、counter とは無関係に呼ばれます。 メソッドの中に書いてあっても、this は引き継がれません。

import / export のあるファイル(モジュール)は、書かなくても strict モードです。 実務では下の例外になるほうが普通です。

Cannot read properties of undefined

アロー関数だけが違う

アロー関数は、自分の this を持ちません。 呼ばれ方に関係なく、書かれた場所this をそのまま使います。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8

addAllcounter.addAll() として呼ばれているので、そこでの thiscounter です。 アロー関数はそれをそのまま借ります

「呼び方で決まる」という規則の唯一の例外がこれです。

だからメソッドには使わない

arrow が書かれているのはオブジェクトリテラルの中で、そこはまだ user の中ではありません。 借りてくるのは外側の this です。user にはなりません。

使う場所 選ぶもの
オブジェクトのメソッド method() {}
メソッドの中のコールバック アロー関数
クラスのフィールドに置く関数 アロー関数(this が固定される)

自分で渡す

call() / apply() / bind() で、this を指定できます。

bind() で作った関数は、あとから call() しても変わりません。 一度結んだら外れないので、コールバックとして渡すときに使えます。

よくある誤解

「メソッドの中に書けば this はそのオブジェクト」

違います。書いた場所は関係ありません。 上の forEach の例がそれで、メソッドの中に書いてあっても外れます。

「アロー関数は this を親から受け取る」

正確には、受け取っていませんそもそも持っていないので、 this と書かれたら外側のスコープを見に行くだけです。

だから call() で渡しても効きません。

{ name: 'い' }無視されました

this を使わなければ安全」

そのとおりです。渡さなくて済む形にできるなら、そのほうが単純です。

引数で受け取れば、呼び方に左右されません。

自分で確かめる

上のコードは書き換えて実行できます。次を試すと違いが見えます。

  1. 'use strict' の行を消すと、undefinedglobalThis に変わる
  2. forEach((x) => ...)forEach(function (x) { ... }) に戻すと落ちる
  3. bind(other) のあとに .call(user) しても変わらない

まとめ

  • this呼ばれた瞬間に決まる。書いた場所は関係ない
  • obj.method() なら obj、取り出して fn() なら渡すものが無い
  • 渡すものが無いとき、sloppy では globalThisstrict では undefined
  • アロー関数は this を持たない。外側のものをそのまま使う
  • メソッドはふつうの書き方、その中のコールバックはアロー関数

コールバックにアロー関数を渡す形は、非同期処理をまとめて走らせるときにもよく出ます。 → 複数の非同期処理を同時に走らせる

この記事の根拠

  1. EvaluateCall — ECMAScript® 2026 Language Specificationtc39.es
  2. OrdinaryCallBindThis — ECMAScript® 2026 Language Specificationtc39.es
  3. Arrow Function Definitions — ECMAScript® 2026 Language Specificationtc39.es

掲載しているコード例は、公開前に読み手が押したときに動くのと同じもので実行して出力を突き合わせています。結果はリポジトリに残しています。

このページの原文(Markdown)