【JavaScript】this とは何者か

クラス内の this
基本的に、メソッドは object.method()
の形で呼び出す。
呼び出されたメソッドの中で、this が何であるかはこのときに決まる。
具体的には、.
の1つ左のオブジェクトがその中での this になる。
下記の例では、this は shion になる。
const shion = new User("shion", 99);
shion.isAdult();
このように、this は同じクラスから生み出される複数のインスタンスオブジェクトがそれぞれ独立し、自分自身のデータを参照できるようにするための仕組みである。

オブジェクト内の this
無論、this はクラス以外でも使用できる。
以下の例では、this をオブジェクトの中で使用している。
const user = {
name: "shion",
age: 99,
isAdult() {
return this.age >= 20;
},
};
console.log(user.isAdult()); // true
この場合、.
の1つ左のオブジェクトがその中での this になるため、メソッド内の this は user を指すことになる。

アロー関数内の this
結論から言うと、アロー関数は自分自身の this を持たない。
自分自身の this が存在しないため、アロー関数では this を外側の関数から受け継ぐことになる。
以下の例では、fn
は、object.method()
の形で呼ばれていないため、this が何を指しているのかわからず undefined が出力される。
arrowFn
は、外側がグローバルスコープなので window オブジェクトが出力される(実行環境がブラウザの場合)。
"use strict";
function fn() {
console.log(this);
}
const arrowFn = () => {
console.log(this);
};
fn(); // undefined
arrowFn(); // window

アロー関数内の this をより詳細に
以下を例にする。
class User {
name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public isAdult(): boolean {
return this.age >= 20;
}
public filterOlder(users: readonly User[]) {
return users.filter(u => u.age > this.age);
}
}
const shion = new User("shion", 24);
const john = new User("John Smith", 15);
const bob = new User("Bob", 40);
const older = shion.filterOlder([john, bob]);
console.log(older); // [User: { "name": "Bob", "age": 40}]
u => u.age > this.age
のthisは、fileterOlder
メソッド内での this と同じとなる。なぜなら、アロー関数内の this は外側の関数の this を受け継いでおり、今回外側の関数とはfilterOlder
だからである。
shion.filterOlder()
の形で呼ばれた場合は、メソッド内での this は shion になる。
外側の関数の this を受け継ぐというアロー関数の性質により、u => u.age > this.age
というアロー関数の中の this も shion となる。これにより、shion よりも age が大きいものを抽出した配列が返ることになる。

アロー関数を使わない書き方
上記の例をアロー関数を使わない書き方で書くと少し面倒なことが起きる。
class User {
name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public isAdult(): boolean {
return this.age >= 20;
}
public filterOlder(users: readonly User[]) {
return users.filter(function(this: User, v) {
return v.age > this.age // Cannot read properties of undefined (reading 'age')
});
}
}
const shion = new User("shion", 24);
const john = new User("John Smith", 15);
const bob = new User("Bob", 40);
const older = shion.filterOlder([john, bob]);
console.log(older);
this が undefined になってしまい、エラーが発生した。
なぜなら、実際にその関数を呼び出すのはusers.filterであり、users.filterは shion のことなど知らないから、users.filter から呼び出されたコールバック関数内で this が shion になることを期待するのは無理筋である。
結局、この関数式内で filterOlder 内の this を参照するためには、あらかじめ this を別の変数に退避しておく必要がある。このような退避用によく使われるのが _this。このようにすれば期待通りに動作する。
public filterOlder(users: readonly User[]) {
const _this = this;
return users.filter(function(u) { return u.age > _this.age });
}
とまあ、こんなテクニックは過去のもの。
なぜなら自分自身の this を持たないアロー関数を使えば良いからね。

this を操作するメソッド
関数の中での this を指定しつつ関数呼び出しを行う、apply
(call), bind
の2つを抑える。

apply
func.apply(obj, args)
の形で呼び出すことで、「関数 func を、中での this を obj にして呼び出す」という意味となる。
引数 args は func に与えられる引数全てを配列に入れたもの。例を見た方が早い。
class User {
name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public isAdult(): boolean {
return this.age >= 20;
}
}
const shion = new User('shion', 24);
const john = new User('John Smith', 15);
console.log(shion.isAdult()); // true
// shion.isAdult を、john を this として呼び出す
console.log(shion.isAdult.apply(john, [])); // false

call
apply と意味は同じ。
唯一の違いは、引数を第二引数に配列で渡していたのを、call()
ではそのまま渡す点。
func.apply(obj, [1, 2, 3]);
func.call(obj, 1, 2, 3);

bind
bind は、this が固定されている新しい関数オブジェクトを作るという効果を持つ。
例えば、func.bind(obj)
のようにした場合、返り値として「呼び出し時の this が obj に固定された func 関数」が得られる。こうして得られた関数は、呼び出し方に左右されず常に this が obj になる。
class User {
name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public isAdult(): boolean {
return this.age >= 20;
}
}
const shion = new User('shion', 24);
const john = new User('John Smith', 15);
// this が shion に固定された isAdult
const boundIsAdult = shion.isAdult.bind(shion)
console.log(boundIsAdult()) // true
console.log(boundIsAdult.call(john)); // true
boundIsAdult.call(john)
としても true が返る点がポイント。
これは呼び出し方に関わらず boundIsAdult()
の中では常に this は shion であり、shion.age ≥ 20
の評価結果が返されるからである。