JavaScriptの this とは、「その関数がどのように呼び出されたか」によって値が決まる特別なキーワードです。 関数をどこに書いたかではなく、呼び出すときの形(obj.method() なのか、fn() なのか、new Fn() なのか)で決まります。例外はアロー関数で、こちらは自分の this を持たず、書かれた場所の外側の this をそのまま使います。

この記事では、「関数呼び出し/メソッド/new/call・apply・bind/アロー関数/イベントハンドラ/クラス」ごとの this を strict・非strict で並べた早見表を冒頭に置きました。そのうえで、メソッドを変数に代入したり、setTimeout やコールバックに渡したりして this が変わってしまう例を再現し、bind・アロー関数・クラスフィールドの3通りの直し方を実行結果つきで示します。call/apply/bind は同じ処理を3通りで書いた比較表にまとめました。コードは Node.js v22.22.2 と Chromium 141(ヘッドレス)で実行して確かめています。

thisの早見表(strict・非strict別)

「呼び出し方」を左から探せば、this が何になるかがわかる表です。すべて実行して確認しました。

呼び出し方 例 非strictでの this strictでの this
関数呼び出し show() グローバルオブジェクト(globalThis) undefined
メソッド呼び出し obj.show() obj obj
new new Maker() 新しく作られたオブジェクト 新しく作られたオブジェクト
call / apply show.call(obj) obj obj
call(null) / call(undefined) show.call(null) グローバルオブジェクトに置き換わる null のまま
call(5)(プリミティブ) show.call(5) Number オブジェクトに包まれる(typeof は "object") 5 のまま("number")
bind show.bind(obj)() obj(あとから変えられない) obj(あとから変えられない)
アロー関数 () => this 外側のスコープの this 外側のスコープの this
イベントハンドラ(function) btn.addEventListener("click", function () {}) イベントを登録した要素 イベントを登録した要素
イベントハンドラ(アロー関数) btn.addEventListener("click", () => {}) 外側のスコープの this 外側のスコープの this
クラスのコンストラクター・メソッド instance.method() (クラスの中は常に strict) インスタンス
クラスの静的メソッド Animal.create() (同上) クラス自体
クラスのメソッドを取り出して呼ぶ const m = instance.method; m() (同上) undefined

トップレベル(関数の外)の this は、ファイルの読み込まれ方で決まります。

場所 トップレベルの this
ブラウザの <script>(クラシックスクリプト) window
ブラウザの <script type="module"> undefined
Node.js の CommonJS(.js、require を使う形) module.exports(最初は {})
Node.js の ES モジュール(.mjs、"type": "module") undefined

よく出会う場面を逆引きにした表も置いておきます。

こんなとき this の値 直し方
メソッドを変数に入れて呼んだ undefined(strict・クラス) bind/アロー関数で包む
setTimeout(obj.method, 100) Node.js は Timeout オブジェクト、ブラウザは window setTimeout(() => obj.method(), 100)
arr.map(function () { this... }) undefined(strict) アロー関数にする/第2引数に this
クリック時にクラスのメソッドを直接渡した クリックされた要素 クラスフィールド+アロー関数
オブジェクトリテラルの中のアロー関数 オブジェクトではなく外側の this メソッド記法 method() {} にする

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JavaScript thisとは?早見表とbind・call・apply」

thisが決まる4つのルールと優先順位

this を読み解くときは、関数が呼び出されている行を見て、次の順に当てはめます。

flowchart TD
    A[その関数はアロー関数?] -- はい --> B[外側のスコープの this]
    A -- いいえ --> C[new で呼んでいる?]
    C -- はい --> D[新しく作られたオブジェクト]
    C -- いいえ --> E[bind 済み、または call / apply で呼んでいる?]
    E -- はい --> F[指定されたオブジェクト]
    E -- いいえ --> G["obj.method() の形?"]
    G -- はい --> H[ドットの左側のオブジェクト]
    G -- いいえ --> I["strict: undefined<br>非strict: globalThis"]
優先 ルール 呼び出しの形 this
0 アロー関数 () => {} 定義された場所の外側の this(呼び出し方は影響しない)
1 new new Fn() 新しいオブジェクト
2 明示的な指定 fn.call(x)、fn.apply(x)、fn.bind(x)() x
3 メソッド呼び出し obj.fn() obj
4 関数呼び出し fn() strict は undefined、非strict は globalThis

MDN でも「this の値は、関数がどのように定義されたかではなく、どのように呼び出されたかによって決まる」という趣旨で説明されています。「定義ではなく呼び出し」が合言葉です。

呼び出し方ごとのthisを実行して確かめる

非strictモードでの結果

// t1.js … "use strict" を書いていない CommonJS のファイル
function show() { return this; }
const obj = { name: "obj", show };
function Maker() { this.made = true; }
const arrow = () => this;

console.log("1 関数呼び出し     :", show() === globalThis);
console.log("2 メソッド         :", obj.show() === obj);
console.log("3 new              :", new Maker());
console.log("4 call(null)       :", show.call(null) === globalThis);
console.log("4 call(5)          :", show.call(5), typeof show.call(5));
console.log("5 アロー関数       :", arrow() === module.exports, arrow.call(obj) === module.exports);
console.log("6 トップレベル     :", this === module.exports, this);
1 関数呼び出し     : true
2 メソッド         : true
3 new              : Maker { made: true }
4 call(null)       : true
4 call(5)          : [Number: 5] object
5 アロー関数       : true true
6 トップレベル     : true {}

strictモードでの結果

先頭に "use strict"; を足しただけの同じコードです。

// t2.js
"use strict";
function show() { return this; }
const obj = { name: "obj", show };
function Maker() { this.made = true; }
const arrow = () => this;

console.log("1 関数呼び出し     :", show());
console.log("2 メソッド         :", obj.show() === obj);
console.log("3 new              :", new Maker());
console.log("4 call(null)       :", show.call(null));
console.log("4 call(5)          :", show.call(5), typeof show.call(5));
console.log("5 アロー関数       :", arrow() === module.exports, arrow.call(obj) === module.exports);
console.log("6 トップレベル     :", this === module.exports);
try { Maker(); } catch (e) { console.log("new なし           :", e.constructor.name + ": " + e.message); }
1 関数呼び出し     : undefined
2 メソッド         : true
3 new              : Maker { made: true }
4 call(null)       : null
4 call(5)          : 5 number
5 アロー関数       : true true
6 トップレベル     : true
new なし           : TypeError: Cannot set properties of undefined (setting 'made')

2つの結果で違うのは「関数呼び出し」「call(null)」「call(5)」の3か所です。

項目 非strict strict
show() globalThis undefined
show.call(null) globalThis に置き換え null
show.call(5) Number オブジェクトに変換 5
new を付け忘れた Maker() グローバルに made が作られる TypeError で気づける

strict モードは、this を勝手に置き換えず、渡された値をそのまま使います。書き間違いをエラーとして早く知らせてくれるので、原因を追いやすくなります。

strictモードになる条件

書き方 strict になる範囲
ファイル先頭の "use strict"; そのファイル全体
関数の先頭の "use strict"; その関数の中
ES モジュール(import/export、type="module"、.mjs) 自動で全体
class の中身 自動で常に

ES モジュールでは、トップレベルの this も undefined です。

// t3.mjs
function show() { return this; }
const arrow = () => this;
console.log("トップレベル :", this);
console.log("関数呼び出し :", show());
console.log("アロー関数   :", arrow());
トップレベル : undefined
関数呼び出し : undefined
アロー関数   : undefined

クラスの中は、ファイルに "use strict" を書いていなくても strict です。次のコードで確かめました。

// u1.js … "use strict" を書いていないファイル
class Greeter {
  constructor(name) { this.name = name; }
  hello() { return `こんにちは、${this.name}`; }
}
const g = new Greeter("佐藤");
const hello = g.hello;
try { hello(); } catch (e) { console.log(e.constructor.name + ": " + e.message); }

function plain() { return this === globalThis; }
console.log("通常の関数:", plain());
TypeError: Cannot read properties of undefined (reading 'name')
通常の関数: true

同じファイルの中でも、通常の関数は非strict(this は globalThis)、クラスのメソッドは strict(this は undefined)として動いています。

アロー関数のthis|外側のthisをそのまま使う

アロー関数は自分の this を持ちません。 書かれた場所の外側の this を、変数を参照するのと同じ感覚でそのまま使います。そのため、call・apply・bind で this を指定しても変わりません(上の実行結果で arrow.call(obj) === module.exports が true でした)。

メソッドの中のコールバックで力を発揮する

// t7.js
"use strict";
const cart = {
  taxRate: 0.1,
  prices: [100, 200, 300],
  withTaxFunction() {
    return this.prices.map(function (p) {
      return Math.round(p * (1 + this.taxRate));
    });
  },
  withTaxArrow() {
    return this.prices.map((p) => Math.round(p * (1 + this.taxRate)));
  },
  withTaxThisArg() {
    return this.prices.map(function (p) {
      return Math.round(p * (1 + this.taxRate));
    }, this);
  },
  withTaxSelf() {
    const self = this;
    return this.prices.map(function (p) {
      return Math.round(p * (1 + self.taxRate));
    });
  },
};
try { cart.withTaxFunction(); } catch (e) { console.log(e.constructor.name + ": " + e.message); }
console.log(cart.withTaxArrow());
console.log(cart.withTaxThisArg());
console.log(cart.withTaxSelf());
TypeError: Cannot read properties of undefined (reading 'taxRate')
[ 110, 220, 330 ]
[ 110, 220, 330 ]
[ 110, 220, 330 ]

map に渡した function は、map の内部から「ただの関数」として呼ばれるため、strict では this が undefined になります。アロー関数にすると、外側(withTaxArrow メソッド)の this、つまり cart をそのまま使えるので、1行で書けます。

方法 書き方 特徴
アロー関数 map((p) => ... this.taxRate) いちばん短い。現在の標準的な書き方
第2引数 thisArg map(function (p) {...}, this) map・forEach・filter などが対応
変数に退避 const self = this; アロー関数が登場する前からある書き方。既存コードでよく見かける

アロー関数が向かない場所

// u4.js(抜粋)
"use strict";
const user = {
  name: "佐藤",
  arrow: () => this.name,
  method() { return this.name; },
};
console.log(user.arrow(), user.method());
undefined 佐藤

オブジェクトリテラルの { } はスコープを作らないので、arrow の外側はファイルのトップレベルです。this は user ではなくトップレベルの this(CommonJS では module.exports)になり、name は見つかりませんでした。

場所 おすすめの書き方 理由
オブジェクトのメソッド method() { ... } 呼び出し元のオブジェクトを this にしたい
メソッドの中のコールバック アロー関数 外側の this を引き継ぎたい
イベントの対象要素を this で使いたい function () { ... } 要素が this に入る
コンストラクター class または function アロー関数は new できない
プロトタイプに置くメソッド function/メソッド記法 インスタンスを this にしたい

thisが変わってしまう例を再現する

ここからは、「さっきまで動いていたメソッドが、渡し方を変えたら動かなくなった」という場面を3つ再現します。原因はどれも同じで、obj.method() の形で呼ばれなくなったことです。

再現1:メソッドを変数に代入する

// t4.js
"use strict";
const counter = {
  count: 0,
  increment() {
    this.count++;
    return this.count;
  },
};

console.log(counter.increment());   // メソッドとして呼ぶ
const inc = counter.increment;      // 変数に代入
try {
  inc();
} catch (e) {
  console.log(e.constructor.name + ": " + e.message);
}
const { increment } = counter;      // 分割代入でも同じ
try { increment(); } catch (e) { console.log(e.constructor.name + ": " + e.message); }
1
TypeError: Cannot read properties of undefined (reading 'count')
TypeError: Cannot read properties of undefined (reading 'count')

inc は counter.increment と同じ関数ですが、inc() にはドットの左側がありません。 そのため「関数呼び出し」のルールが当てはまり、strict では this が undefined になります。

同じコードから "use strict" を外すと、例外は出ずに次の結果になりました。

NaN
0

非strict では this が globalThis になり、globalThis.count(未定義)に ++ をして NaN になります。counter.count は 0 のまま変わっていません。エラーが出ないぶん、気づくのが遅くなりやすい動きです。strict(またはクラス・ES モジュール)で書いておくと、TypeError としてその場で知らせてもらえます。

再現2:コールバックとして渡す

// u2.js
"use strict";
class Greeter {
  constructor(name) { this.name = name; }
  hello() { console.log(`こんにちは、${this.name}`); }
}
function runLater(callback) {
  callback();              // ただの関数呼び出しになる
}
const g = new Greeter("佐藤");
try { runLater(g.hello); } catch (e) { console.log(e.constructor.name + ": " + e.message); }
TypeError: Cannot read properties of undefined (reading 'name')

runLater(g.hello) は、関数だけを取り出して渡している状態です。受け取った側は callback() と呼ぶので、g とのつながりは残りません。

再現3:setTimeoutに渡す

setTimeout に渡した場合は、実行環境によって this の中身が変わります。

// u3.js(Node.js で実行)
"use strict";
class Greeter {
  constructor(name) { this.name = name; }
  hello() { console.log(`こんにちは、${this.name}`); }
}
const g = new Greeter("佐藤");
setTimeout(g.hello, 10);
setTimeout(function () { console.log(this.constructor.name, this === globalThis); }, 20);
こんにちは、undefined
Timeout false

Node.js では、setTimeout のコールバックの this は Timeout オブジェクトでした。undefined ではないので TypeError にはならず、this.name が undefined として表示されます。

ブラウザ(Chromium 141)でも確かめたところ、strict な関数であっても setTimeout のコールバックの this は window でした。

setTimeout strict function: this===window true
setTimeout function: this===window true
環境 setTimeout(obj.method, ms) での this 見え方
Node.js v22 Timeout オブジェクト プロパティが undefined になる(例外にならない)
ブラウザ window 同上

「undefined と表示される」「NaN になる」という形で現れるので、タイマーに渡したメソッドの結果がおかしいときは、まず this を疑うと早く解決できます。

3通りの直し方|bind・アロー関数・クラスフィールド

同じクラスに対して、3通りの直し方を並べて実行しました。

// t6.js
"use strict";
class Timer {
  constructor(label) {
    this.label = label;
    this.ticks = 0;
    this.tickBound = this.tick.bind(this);     // 直し方1': コンストラクターで bind
  }
  tick() {
    this.ticks++;
    console.log(`${this.label}: ${this.ticks}`);
  }
  tickField = () => {                           // 直し方3: クラスフィールド+アロー関数
    this.ticks++;
    console.log(`${this.label}: ${this.ticks}`);
  };
}
const t = new Timer("A");
setTimeout(t.tick.bind(t), 10);      // 直し方1: 渡すときに bind
setTimeout(t.tickBound, 20);         // 直し方1': コンストラクターで bind 済み
setTimeout(() => t.tick(), 30);      // 直し方2: アロー関数で包む
setTimeout(t.tickField, 40);         // 直し方3: クラスフィールド
A: 1
A: 2
A: 3
A: 4

4回とも this が t を指し、カウントが正しく進みました。

直し方1:bindでthisを固定する

fn.bind(obj) は、this が obj に固定された新しい関数を返します。渡す側で t.tick.bind(t) と書くか、コンストラクターで this.tickBound = this.tick.bind(this) としておきます。

直し方2:アロー関数で包む

() => t.tick() は、呼び出しの形を t.tick() のまま保つ方法です。引数を足したいときも () => t.tick(1, 2) のように自然に書けます。

直し方3:クラスフィールドにアロー関数を置く

tickField = () => { ... } と書くと、インスタンスを作るたびに、そのインスタンスを this として覚えたアロー関数がプロパティとして作られます。渡す側は t.tickField と書くだけで済みます。

3つの比較と選び方

// u4.js(抜粋)
class A { name = "A"; f = () => this.name; m() { return this.name; } }
const a = new A();
console.log(Object.hasOwn(a, "f"), Object.hasOwn(a, "m"), Object.hasOwn(A.prototype, "m"));
console.log(new A().f === new A().f, new A().m === new A().m);
true false true
false true

クラスフィールドのアロー関数 f はインスタンスごとに別の関数として作られ、通常のメソッド m はプロトタイプ上の1つを全インスタンスで共有していることがわかります。

直し方 書く場所 関数の作られ方 向いている場面
bind 渡す側、またはコンストラクター bind のたびに新しい関数 クラスを変更できない/一部の呼び出しだけ直したい
アロー関数で包む 渡す側 その場で新しい関数 引数を足したい/その場限りのコールバック
クラスフィールド クラスの定義 インスタンスごとに1つ イベントハンドラなど、何度も渡すメソッド

イベントリスナーを後で解除する(removeEventListener)場合は、登録時と同じ関数を渡す必要があります。 渡すたびに bind したりアロー関数で包んだりすると毎回別の関数になるため、クラスフィールドやコンストラクターでの bind のように、1度作った関数をプロパティに持っておく形が扱いやすくなります。

コールバックを受け取る関数に対する3通りの結果も確認しました。

// u2.js(続き)
runLater(g.hello.bind(g));
runLater(() => g.hello());
["a"].forEach(g.hello, g);     // 第2引数で this を指定できる API もある
こんにちは、佐藤
こんにちは、佐藤
こんにちは、佐藤

call・apply・bindの違いを同じ処理で比べる

3つとも「this を自分で指定する」ための関数のメソッドです。同じ処理を3通りで書きました。

// t8.js(抜粋)
"use strict";
function introduce(greeting, mark) {
  return `${greeting}、${this.name}です${mark}`;
}
const sato = { name: "佐藤" };

console.log(introduce.call(sato, "こんにちは", "!"));
console.log(introduce.apply(sato, ["こんにちは", "!"]));
const bound = introduce.bind(sato, "こんにちは");
console.log(bound("!"));
こんにちは、佐藤です!
こんにちは、佐藤です!
こんにちは、佐藤です!
メソッド 書き方 引数の渡し方 実行のタイミング 戻り値
call fn.call(thisArg, a, b) カンマ区切りで並べる すぐ実行 関数の戻り値
apply fn.apply(thisArg, [a, b]) 配列(配列風オブジェクト)で1つ すぐ実行 関数の戻り値
bind fn.bind(thisArg, a) カンマ区切り(先頭から固定できる) 実行しない this を固定した新しい関数

覚え方は、「call はカンマ、apply は配列(Array の A)、bind は後で呼ぶ」です。

スプレッド構文があればcallで足りる場面が多い

const args = ["こんにちは", "!"];
console.log(introduce.call(sato, ...args));
console.log(Math.max.apply(null, [3, 9, 4]), Math.max(...[3, 9, 4]));
こんにちは、佐藤です!
9 9

配列を引数に展開する用途は、スプレッド構文 ... で書けるようになりました。Math.max.apply(null, 配列) は Math.max(...配列) と同じ結果です。

bindの性質|一度固定したthisは変わらない

// t8.js / u4.js(抜粋)
console.log(bound.call({ name: "鈴木" }, "?"));
console.log(bound.bind({ name: "鈴木" })("?"));
console.log(bound.name, introduce.length, bound.length);

function price(rate, amount) { return Math.round(amount * (1 + rate)); }
const withTax = price.bind(null, 0.1);
console.log(withTax(1000));
こんにちは、佐藤です?
こんにちは、佐藤です?
bound introduce 2 1
1100
性質 確認した結果
bind 済みの関数に call で別の this を渡す 最初の this(佐藤)のまま
bind 済みの関数をもう一度 bind する 最初の this(佐藤)のまま
関数名 bound introduce(bound が前に付く)
引数の数(length) 固定したぶん減る(2 → 1)
引数だけを固定する使い方 price.bind(null, 0.1) で「率を固定した関数」を作れる

アロー関数にはcall・apply・bindのthis指定が効かない

const arrow = () => typeof this;
console.log(arrow.call(sato), arrow.bind(sato)());
object object

sato を渡しても、返ってきたのは外側の this(CommonJS の module.exports)の型でした。アロー関数の this は定義時に決まり、あとから差し替えられません。 引数を渡す目的では call・apply・bind を使えますが、第1引数は無視されます。

どれを使うかの判断表

やりたいこと 使うもの
this を指定して今すぐ1回呼ぶ call
引数が配列で手元にある call(obj, ...args) または apply(obj, args)
あとで呼ばれるコールバックの this を固定する bind(またはアロー関数で包む)
引数の一部を先に決めた関数を作る bind(null, 値)
別のオブジェクトのメソッドを借りる Array.prototype.map.call(配列風, fn)
値の種類を調べる定番 Object.prototype.toString.call(値)

メソッドを借りる例も実行しておきました。

const arrayLike = { 0: "a", 1: "b", length: 2 };
console.log(Array.prototype.map.call(arrayLike, (s) => s.toUpperCase()), Array.from(arrayLike));
console.log(Object.prototype.toString.call([]), Object.prototype.toString.call(null));
[ 'A', 'B' ] [ 'a', 'b' ]
[object Array] [object Null]

イベントハンドラのthis

ブラウザでの動きは、次の HTML を Chromium 141(ヘッドレス)で開き、ボタンをクリックして確かめました。

<button id="btn" onclick="log('inline属性: ' + (this === document.getElementById('btn')) + ' / ' + this.tagName)">押す</button>
<div id="wrap"><button id="child">子</button></div>
<script>
const out = [];
function log(s) { out.push(s); }

const btn = document.getElementById("btn");
btn.addEventListener("click", function (e) {
  log("function: this===btn " + (this === btn) + " / currentTarget " + (this === e.currentTarget));
});
btn.addEventListener("click", (e) => {
  log("arrow: this===window " + (this === window) + " / e.currentTarget===btn " + (e.currentTarget === btn));
});
document.getElementById("wrap").addEventListener("click", function (e) {
  log("親で受ける: this=" + this.id + " target=" + e.target.id + " currentTarget=" + e.currentTarget.id);
});
</script>
inline属性: true / BUTTON
function: this===btn true / currentTarget true
arrow: this===window true / e.currentTarget===btn true
親で受ける: this=wrap target=child currentTarget=wrap
登録の仕方 this
addEventListener("click", function () {}) リスナーを登録した要素(e.currentTarget と同じ)
addEventListener("click", () => {}) 外側の this(クラシックスクリプトのトップレベルなら window)
HTML の onclick="..." 属性 その要素
親要素に登録して子をクリック 親要素(クリックされた子は e.target)

アロー関数でも e.currentTarget を使えば、登録した要素を取得できます。 this に頼らず e.currentTarget(登録した要素)と e.target(実際にクリックされた要素)を使い分けると、関数の種類を問わず同じ書き方で済みます。

クラスのメソッドをリスナーに渡すとき

<button id="b2">クラス</button>
<script>
class Counter {
  constructor() { this.count = 0; }
  handleClick() { this.count++; log("handleClick: count=" + this.count); }
  handleField = () => { this.count++; log("handleField: count=" + this.count); };
}
const c = new Counter();
const b2 = document.getElementById("b2");
b2.addEventListener("click", c.handleClick);            // そのまま渡す
b2.addEventListener("click", c.handleClick.bind(c));    // bind
b2.addEventListener("click", () => c.handleClick());    // アロー関数で包む
b2.addEventListener("click", c.handleField);            // クラスフィールド
</script>

1回クリックした結果です。

handleClick: count=NaN
handleClick: count=1
handleClick: count=2
handleField: count=3

そのまま渡した1つ目は、this がボタン要素になり、ボタン.count(未定義)に ++ をして NaN になりました。例外は出ない点に注意が必要です。残りの3つは this が c を指し、カウントが 1、2、3 と進んでいます。

Node.js の EventTarget と EventEmitter でも同じ考え方で、function で登録したリスナーの this は登録先のオブジェクトでした(function: true、emitter function: true を確認)。

クラスの中のthis

// t9.js(抜粋)
"use strict";
class Animal {
  static count = 0;
  static create(name) { Animal.count++; return new this(name); }
  static who() { return this === Animal; }
  constructor(name) { this.name = name; }
  speak() { return `${this.name} が鳴く`; }
}
class Dog extends Animal {
  constructor(name) {
    try { this.x = 1; } catch (e) { console.log(e.constructor.name + ": " + e.message); }
    super(name);
    this.kind = "dog";
  }
  speak() { return super.speak() + "(ワン)"; }
}
const d = Dog.create("ポチ");
console.log(d, d.speak(), Animal.who(), Dog.who());
try { Animal("x"); } catch (e) { console.log(e.constructor.name + ": " + e.message); }
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Dog { name: 'ポチ', kind: 'dog' } ポチ が鳴く(ワン) true false
TypeError: Class constructor Animal cannot be invoked without 'new'
場所 this 確認できたこと
コンストラクター 作成中のインスタンス this.name = name でプロパティが付く
インスタンスメソッド ドットの左のインスタンス d.speak() で d
静的メソッド 呼び出したクラス Dog.create() の中の new this() は Dog を作る。Dog.who() は this === Animal が false
継承先のコンストラクター super() を呼んだ後から使える 先に触ると ReferenceError
クラスを new なしで呼ぶ — TypeError

継承したクラスのコンストラクターでは、this を使う前に super() を呼ぶのが決まりです。エラーメッセージもそのまま「先に super を呼んでください」と教えてくれます。

メソッドチェーンとthisを返す書き方

const chain = { v: 0, add(n) { this.v += n; return this; } };
console.log(chain.add(1).add(2).v);
3

メソッドの最後で return this; とすると、戻り値に続けて次のメソッドを呼べます。

入れ子のオブジェクトと「ドットの直前」

const a = { name: "a", b: { name: "b", who() { return this.name; } } };
console.log(a.b.who());
b

a.b.who() の this は、ドットのいちばん右、関数の直前にあるオブジェクト(a.b)です。

よくあるつまずきと直し方

症状・エラー文 原因 直し方
TypeError: Cannot read properties of undefined (reading 'xxx') メソッドを取り出して呼び、this が undefined bind/アロー関数で包む/クラスフィールド
TypeError: Cannot set properties of undefined (setting 'xxx') コンストラクター関数を new なしで呼んだ(strict) new を付ける/class にする
表示が undefined や NaN になる(例外なし) setTimeout やイベントに渡して this が別のオブジェクトになった () => obj.method() で包む
オブジェクトのアロー関数で this.name が undefined アロー関数は外側の this を使う メソッド記法 method() {} にする
map(function () { this... }) で TypeError コールバックは関数呼び出し アロー関数にする
ReferenceError: Must call super constructor ... super() の前に this に触れた super() を先頭で呼ぶ
removeEventListener しても解除されない 登録時と別の関数(毎回 bind)を渡している 1度作った関数をプロパティに保持して渡す

thisを読み解くチェックリスト

  • [ ] 関数が呼び出されている行を見つけた
  • [ ] その関数はアロー関数か(アロー関数なら外側の this)
  • [ ] new が付いているか
  • [ ] call/apply/bind で指定されているか
  • [ ] obj.method() の形か(ドットの直前は何か)
  • [ ] どれでもなければ、strict か非strict かを確認した
  • [ ] 関数を「渡している」箇所(setTimeout、addEventListener、map など)で () なしになっていないか確認した

よくある質問

Q. this がわからなくなったら、まず何をすればよいですか? A. 関数の先頭に console.log(this) を入れて、実際の値を見るのが近道です。そのうえで「この関数はどの行から、どんな形で呼ばれているか」をたどると、早見表のどの行に当たるかがわかります。

Q. アロー関数と通常の関数は、どちらを使えばよいですか? A. コールバック(map、setTimeout、then など)にはアロー関数、オブジェクトやクラスのメソッドにはメソッド記法、と分けると迷いにくくなります。イベントの対象要素を this で受け取りたいときだけ function を使います(e.currentTarget を使えばアロー関数でも対応できます)。

Q. globalThis とは何ですか? A. 実行環境を問わずグローバルオブジェクトを指す名前です。ブラウザでは window、Node.js では global と同じものを指します(Node.js で globalThis === global が true になることを確認しました)。

Q. bind とクラスフィールドのアロー関数は、どちらがよいですか? A. 自分で書くクラスで、イベントハンドラのように何度も渡すメソッドなら、クラスフィールドが簡潔です。既存のクラスを変更せずに使う場合や、一部の呼び出しだけ this を固定したい場合は bind が向いています。どちらもインスタンスごとに関数を1つ持つ点は同じです。

まとめ

  • this は「関数がどう呼ばれたか」で決まる。obj.method() ならドットの直前のオブジェクト
  • ただの関数呼び出し fn() では、strict は undefined、非strict は globalThis。クラスと ES モジュールは常に strict
  • アロー関数は自分の this を持たず、外側の this を使う。call・apply・bind でも変わらない
  • メソッドを変数・コールバック・setTimeout・イベントに渡すと this が変わる。TypeError のほか、undefined や NaN として現れることもある
  • 直し方は3通り:bind で固定する、アロー関数で包む、クラスフィールドにアロー関数を置く
  • call はカンマ区切りで即実行、apply は配列で即実行、bind は固定した関数を返す
  • イベントハンドラの this は登録した要素。e.currentTarget を使えば関数の種類を問わず同じ書き方にできる

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JavaScript thisとは?早見表とbind・call・apply」

株式会社RJCでは、JavaScriptのthisのような基礎を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?