JavaScriptの学習、お疲れ様です。他の言語(Java, C#, Pythonなど)の経験がある方にとって、JavaScriptの this は最も直感に反し、バグの温床となりやすい機能の一つです。
多くの言語において this(または self)は「そのクラスのインスタンス」を指す静的なものですが、JavaScriptの this は「関数がどのように呼ばれたか」によって動的に変化します。
この章では、その複雑な挙動を解き明かし、自在にコントロールする方法を学びます。
JavaScriptにおける関数(アロー関数を除く)の this は、**「定義された場所」ではなく「呼び出された場所(Call Site)」**によって決定されます。
大きく分けて、以下の3つのパターンを理解すれば基本は押さえられます。
オブジェクトのプロパティとして関数を呼び出した場合(obj.method())、this はドットの左側のオブジェクト(レシーバ)になります。これは他の言語のメンバ関数に近い挙動です。
関数を単体で呼び出した場合(func())、this はグローバルオブジェクト(ブラウザでは window、Node.jsでは global)になります。
ただし、**Strict Mode("use strict")**では、安全のため undefined になります。
new キーワードをつけて呼び出した場合、this は新しく生成されたインスタンスになります(これは第6章、第7章で詳しく扱います)。
以下のコードで、同じ関数でも呼び出し方によって this が変わる様子を確認しましょう。
"use strict"; // Strict Modeを有効化
function showThis() {
console.log(`this is: ${this}`);
}
const person = {
name: "Alice",
show: showThis,
toString: function() { return this.name; } // コンソール出力用に設定
};
// 1. メソッド呼び出し
console.log("--- Method Call ---");
person.show();
// 2. 関数呼び出し(変数に代入してから実行)
console.log("--- Function Call ---");
const standaloneShow = person.show;
standaloneShow(); node dynamic-this.js--- Method Call --- this is: Alice --- Function Call --- this is: undefined
ポイント:
person.showをstandaloneShowに代入した時点で、オブジェクトとの結びつきは失われます。そのため、standaloneShow()と実行すると「関数呼び出し」扱いとなり、thisはundefined(非Strict Modeならグローバルオブジェクト)になります。これが「thisが消える」現象の正体です。
「関数呼び出し」でも特定のオブジェクトを this として扱いたい場合があります。JavaScriptには、this を明示的に指定(束縛)するためのメソッドが3つ用意されています。
これらは関数を即座に実行します。第一引数に this としたいオブジェクトを渡します。
call(thisArg, arg1, arg2, ...): 引数をカンマ区切りで渡す。apply(thisArg, [argsArray]): 引数を配列として渡す。> function greet(greeting, punctuation) { return `${greeting}, ${this.name}${punctuation}`; }
undefined
> const user = { name: "Bob" };
undefined
> // callの使用例
> greet.call(user, "Hello", "!");
'Hello, Bob!'
> // applyの使用例
> greet.apply(user, ["Hi", "?"]);
'Hi, Bob?'
bind は関数を実行せず、this を固定した新しい関数を返します。これは、イベントリスナーやコールバック関数としてメソッドを渡す際に非常に重要です。
const engine = {
type: "V8",
start: function() {
console.log(`Starting ${this.type} engine...`);
}
};
// そのまま渡すと this が失われる(関数呼び出しになるため)
const brokenStart = engine.start;
// brokenStart(); // エラー: Cannot read property 'type' of undefined
// bind で this を engine に固定する
const fixedStart = engine.start.bind(engine);
fixedStart();node bind-example.jsStarting V8 engine...
ES2015 (ES6) で導入されたアロー関数は、これまで説明したルールとは全く異なる挙動をします。
アロー関数には独自の this が存在しません。アロー関数内部の this は、その関数が定義されたスコープ(レキシカルスコープ)の this をそのまま参照します。
これは、「コールバック関数内で this が変わってしまう問題」を解決するのに最適です。
setTimeout などのコールバック内でメソッドを使いたい場面を比較してみましょう。
class Timer {
constructor() {
this.seconds = 0;
}
// 従来の方法: 失敗例
startLegacy() {
setTimeout(function() {
// ここでの this はグローバルまたはundefined(setTimeoutの仕様)
// そのため this.seconds にアクセスできずNaNなどになる
try {
this.seconds++;
console.log("Legacy:", this.seconds);
} catch (e) {
console.log("Legacy: Error -", e.message);
}
}, 100);
}
// アロー関数: 成功例
startModern() {
setTimeout(() => {
// アロー関数は定義時のスコープ(startModern内のthis = インスタンス)を捕獲する
this.seconds++;
console.log("Modern:", this.seconds);
}, 100);
}
}
const timer = new Timer();
timer.startLegacy();
timer.startModern();node arrow-vs-function.jsLegacy: Error - Cannot read properties of undefined (reading 'seconds') Modern: 1
注意: アロー関数は便利な反面、
thisを動的に変更することができません(callやbindを使っても無視されます)。そのため、動的なコンテキストが必要な場合(例:オブジェクトのメソッド定義そのものや、ライブラリ等でthisを注入される場合)には通常の関数式を使います。
JavaScriptの this は、他の静的な言語とは異なり「呼び出し時」に解決されます。
obj.method()): this は obj。func()): this は undefined (Strict Mode) またはグローバルオブジェクト。call, apply で一時的に、bind で永続的に this を指定可能。this を持たず、外側のスコープの this をそのまま使う(レキシカルスコープ)。次の章では、この this を活用してオブジェクト指向プログラミングの核心である「オブジェクトとプロトタイプ」について学びます。
以下のコードは、ボタンクリック時(ここではシミュレーション)にユーザー名を表示しようとしていますが、エラーになります。
bind を使って修正してください。greet メソッド自体をアロー関数に変更するアプローチではなく、呼び出し側を修正する形で解答してください。const user = {
name: "Tanaka",
greet: function() {
console.log(`Hello, ${this.name}`);
}
};
// クリックイベントのシミュレーター(変更不可)
function simulateClick(callback) {
// 内部で単なる関数呼び出しとして実行される
callback();
}
// --- 以下を修正してください ---
simulateClick(user.greet); node practice5_1.js以下の calculator オブジェクトにはバグがあります。multiply メソッドが正しい結果(配列の各要素を factor 倍する)を返すように修正してください。
ヒント:map の中のコールバック関数に注目してください。
const calculator = {
factor: 2,
numbers: [1, 2, 3],
multiply: function() {
return this.numbers.map(function(n) {
// ここで this.factor が読めない!
return n * this.factor;
});
}
};
try {
console.log(calculator.multiply());
} catch(e) {
console.log("Error:", e.message);
}node practice5_2.js