this とは何か ― 「スコープ」との違い
まず一番大事なポイント:
this は「関数が どう呼ばれたか で決まる特別な値」。
スコープのように「どこに書いたか」で決まるものではない。
よくある誤解:
- 「
thisは“定義されているオブジェクト”を指す」 → ❌ - 「クラスっぽいから Java / C++ と同じ感覚でOK」 → ❌(似てるけどルールが違う)
this は 「呼び出し方(コールサイト)」 で決まる、というところを軸にします。
this が決まる 5 つのパターン
- 通常呼び出し:
func() - メソッド呼び出し:
obj.method() - コンストラクタ呼び出し:
new Func() - 明示的バインド:
func.call(obj)/func.apply(obj)/func.bind(obj) - アロー関数:
thisを持たず、外側のthisを引き継ぐ(レキシカル this)
順番に見ていきます(strict モード前提)。
通常呼び出し func() の this
"use strict";
function showThis() {
console.log(this);
}
showThis(); // undefined- strict モードでは、ただの関数呼び出しの
thisはundefined - 非 strict だとグローバルオブジェクト(
window等)になる
strict 前提で書くなら、
「func() 単体呼び出しの this は基本 undefined」
と覚えてOKです。
メソッド呼び出し obj.method() の this
"use strict";
const obj = {
value: 42,
show: function () {
console.log(this.value);
},
};
obj.show(); // 42obj.show()と呼ぶと、thisは「ドットの左側のオブジェクト」=obj
関数を別のオブジェクトにコピーすると?
const obj2 = {
value: 100,
show: obj.show,
};
obj2.show(); // 100- 同じ関数でも、「どのオブジェクトから呼ばれたか」で
thisが変わる
関数だけ取り出して呼ぶと?
const fn = obj.show;
fn(); // this は undefined(通常呼び出し扱い)→ this は「所有者」ではなく「呼び出し方」で決まる というのが重要です。
コンストラクタ呼び出し new Func() の this
"use strict";
function Person(name) {
this.name = name;
}
const p = new Person("Taro");
console.log(p.name); // "Taro"new Person("Taro") のとき、内部的には:
- 新しい空オブジェクト
{}を作る - そのオブジェクトを
thisにセットした状態でPersonを呼ぶ this.name = nameでプロパティを追加- 何も
returnしなければ、そのthisが返ってくる
なので、
Person("Taro"); // ← new を付け忘れると…strict モードでは this が undefined なので、this.name = ... で TypeError になります。
call / apply / bind による明示的な this 指定
call
"use strict";
function showName() {
console.log(this.name);
}
const user = { name: "Taro" };
const admin = { name: "Admin" };
showName.call(user); // "Taro"
showName.call(admin); // "Admin"- 形式:
func.call(thisArg, arg1, arg2, ...) thisArgをthisにしてfuncを 即座に実行 する
apply
function sum(a, b) {
console.log(this.label, a + b);
}
const ctx = { label: "result:" };
sum.apply(ctx, [2, 3]); // "result: 5"applyは 引数を配列で渡すバージョンのcall- ES2015 以降はスプレッド構文があるので、
call+...で代用しやすい:
sum.call(ctx, ...[2, 3]);bind
function greet() {
console.log(`Hello, ${this.name}`);
}
const greetUser = greet.bind({ name: "Taro" });
greetUser(); // Hello, Taro
greetUser(); // 何度呼んでも this は固定func.bind(thisArg, arg1, ...)は、thisと一部の引数を固定した「新しい関数」 を返す
典型パターン:
const button = {
label: "保存",
onClick() {
console.log(this.label);
},
};
const handler = button.onClick.bind(button);
// handler をイベントリスナーに渡す、などアロー関数の this(レキシカル this)
「自分の this を持たない」
アロー関数は、
自分固有の this を持たず、「外側の this」をそのまま使う
という仕様です。
"use strict";
const obj = {
value: 42,
show: function () {
const inner = () => {
console.log(this.value);
};
inner();
},
};
obj.show(); // 42showの中のthisはobjinner(アロー関数)は「外側スコープのthis」を引き継ぐ
→inner内のthisもobj
コールバックでの典型パターン
const counter = {
value: 0,
start() {
setInterval(() => {
this.value++;
console.log(this.value);
}, 1000);
},
};
counter.start();- もしコールバックを
function () { ... }で書くと、
その中のthisはundefined(strict)やwindowになってしまう - アロー関数を使えば、外側の
this(counter)を素直に引き継げる
メソッド自体をアロー関数にするのは危険
"use strict";
const obj = {
value: 42,
show: () => {
console.log(this.value);
},
};
obj.show(); // undefined(外側がグローバル or モジュールの this)- アロー関数は「外側の
this」を参照するので、
オブジェクトリテラル内メソッドでアローを使うと、objを指さない ことが多い
実務指針:
- メソッド本体は
function/ メソッド記法show() {}を使う - メソッドの中で使うコールバックにはアロー関数を使う
クロージャとは何か(実行コンテキストとの関係)
クロージャの定義
クロージャは:
「関数が、自分の外側スコープの変数を“記憶”している状態」
= 「関数 + その関数がキャプチャしている環境」です。
もう少し踏み込むと:
「ある関数が、実行終了後も外側スコープの変数への参照を保持し続けるとき、
その関数はクロージャになっている」
一番典型的な例:カウンタ
function createCounter() {
let count = 0; // createCounter のローカル変数
return function () {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3何が起きているか:
createCounter()呼び出し →createCounter実行コンテキストが作られる- その中に
let count = 0;がある return function () { ... }で 内部関数 を返すcreateCounterの実行自体は終了するが、
戻り値の関数(counter)がcountへの参照を保持している- そのため
countを含む環境が GC されずに延命される
→ 「内部関数+延命された外側の環境」= クロージャ
createCounter を2回呼ぶと別インスタンスになる
const c1 = createCounter();
const c2 = createCounter();
c1(); // 1
c1(); // 2
c2(); // 1
c2(); // 2createCounter()を呼ぶたびに、新しいcountを持った環境が生まれる- 各関数は、自分専用の
countをキャプチャしている
クロージャの実用パターン
ID発行器
function createIdGenerator(start = 1) {
let current = start;
return function () {
const id = current;
current++;
return id;
};
}
const gen = createIdGenerator(100);
console.log(gen()); // 100
console.log(gen()); // 101
console.log(gen()); // 102currentは外部から直接触れない「プライベート状態」- 返された関数を通してだけ更新できる
プライベート変数を持つオブジェクト
function createUser(name) {
let _name = name; // 外から見えない「プライベート」
return {
getName() {
return _name;
},
setName(newName) {
_name = newName;
},
};
}
const user = createUser("Taro");
console.log(user.getName()); // "Taro"
user.setName("Hanako");
console.log(user.getName()); // "Hanako"
console.log(user._name); // undefined(直接はアクセスできない)createUserのスコープ内に_nameを閉じ込める- 外部からは getter / setter 経由でしか触れない
class + private フィールド(#name)でも同様のことができるけど、
関数 + クロージャだけでも同じ発想が実現できる、という例です。
ループとクロージャの罠(var と let)
var を使ったときの典型的な失敗
const funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 3
funcs[1](); // 3
funcs[2](); // 3理由:
var iは「関数スコープ」なので、ループ全体で 1 つだけ 存在- ループ終了時点で
iは 3 - 3 つの無名関数は、みんな 同じ
iを参照している
→ どれを呼んでも 3
let なら意図どおり
const funcs = [];
for (let i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2let iはブロックスコープ- さらに ES2015 の仕様で、「for ループの
letは各周回ごとに別の束縛を作る」 と決まっている - 各関数は「その周回での
i」をクロージャとして捕まえている
実務指針:
- ループ変数に
varは使わない - 基本
for (let i = ...にしておけば、この罠は避けられる
this とクロージャの使い分け
状態を持つロジックは、
this(インスタンスプロパティ)で持つ- クロージャの中に閉じ込める
どちらでも書けます。
this で持つ(クラス的な設計)
class Counter {
constructor(initial = 0) {
this.value = initial;
}
increment() {
this.value++;
}
}
const c = new Counter(10);
c.increment();
console.log(c.value); // 11特徴:
- インスタンスごとに状態を持つ
- 継承(
extends)やポリモーフィズムと相性が良い - 状態は原則「public」なので、
c.value = 999;もできてしまう(隠蔽したい場合は#value)
クロージャで持つ(カプセル化重視)
function createCounter(initial = 0) {
let value = initial;
return {
increment() {
value++;
},
getValue() {
return value;
},
};
}
const c = createCounter(10);
c.increment();
console.log(c.getValue()); // 11
// c.value; // undefined(外から直接触れない)特徴:
valueは外部から完全に隠せる(API 経由のみ)- 小さなモジュール・ヘルパー関数には書きやすい
- プロトタイプ継承などは使わないシンプル構造
ざっくりした指針
- ライブラリ / クラス設計 / 継承を使うようなオブジェクト指向設計 →
class/this - 小さなユーティリティ・プライベート状態を持つモジュール → クロージャ
どちらも「状態を持つ」という意味では似ていますが、
設計の方向性(オブジェクト指向 vs 関数+カプセル化) が違うイメージです。
コメント