- JavaScript
JavaScript thisとは?早見表とbind・call・apply
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期連続の黒字決算。