【JS】 thisの参照先とcall, apply, bindについて

【JS】 thisの参照先とcall, apply, bindについて

thisとは

thisとはJavaScriptエンジンによって関数コンテキスト内に設定されている「呼び出し元」のオブジェクトへの参照を保持するキーワードを指します。

this は関数が実行される際に値が設定されるためどのように関数が呼ばれたかによって値が異なる可能性があります。

thisの参照先

今回は自身の頭の整理も兼ねて、下記のパターンのthisの参照先についてまとめました。

  • グローバルコンテキスト
  • 関数内
  • コールバック関数内
  • オブジェクトメソッド内
  • メソッド内の入れ子構造
  • グローバルオブジェクトに格納されているメソッド内
  • クラス内
  • bind によるthis”の参照先の指定
  • bind, call, apply の違い
  • アロー関数内

また、今回は「非厳格モード」が前提となります。「厳格モード」では単に関数内実行する場合全てundefinedとなります

グローバルコンテキストでのthis

メソッドや関数などに囲まれていない状態でthisを呼び出すとグローバルオブジェクトであるwindowオブジェクトを参照します。

console.log(this);//▶︎  Window{parent: Window, opener: null, top: Window, length: 0, frames: Window,…}

関数内のthis

単に関数内で呼び出す場合でも、thisはwindowオブジェクトを参照します。

function fn() {
    console.log(this);
}
fn();//▶︎ Window { window: Window, self: Window, document: document, name: '', location: Location, … }

オブジェクトメソッド内のthis

メソッド内で呼び出す場合、thisは呼び出し元のオブジェクト「person」を参照します。
単なる関数内とは異なり、直近で囲まれているオブジェクトを参照することになります。

const person = {
	name: '太郎',
	callName() {
		console.log(`こんにちは ${this.name}さん`);// personを参照
	}
}

person.callName();// => こんにちは 太郎さん

注意点として、person.callNameを新しく関数に定義するとthisはwindowオブジェクトを参照します。

window.name = "グローバル太郎"; //windowオブジェクトにも name を追加
const person = {
	name: '太郎',
	callName() {
		console.log(`こんにちは ${this.name}さん`);// windowオブジェクトを参照する
	}
}

const myFn = person.callName;
myFn();// => こんにちは グローバル太郎さん

新しい関数として const や let で定義した場合、perosnの外に新しくmyFn が生成され
personの外に独立したオブジェクトとしてcallName内の処理が存在しており、person中のcallName メソッドには、外部の関数オブジェクトへのアドレスを保持している、という構造になります。

myFnにperson.callName内のconsole.log(...)へのアドレスがコピーされ、thisを参照しようとします。ですが、オブジェクトへの参照まではコピーされないためmyFnには参照するべきオブジェクトが存在せず、更に外側のグローバルスコープを参照することとなります。

メソッド内で入れ子になった関数内のthis

メソッド内にさらにthisを含む関数を定義するとそのthisはwindowオブジェクトを参照します

window.name = "グローバル太郎";

const person = {
	name: '太郎',
	callName () {
		console.log(`こんにちは ${this.name}さん`);// personオブジェクトを参照する 

		const callName2 = function () {
			console.log(`こんにちは ${this.name}さん`);// windowオブジェクトを参照する
		}();
	}
}

person.callName();
// => こんにちは 太郎さん 
// => こんにちは グローバル太郎さん

コールバック関数としてメソッドを実行する場合のthis

コールバック関数としてperson.callName実行する場合thisはwindowオブジェクトを参照します。

window.name = "グローバル太郎";

const person = {
	name: '太郎',
	callName() {
		console.log(`こんにちは ${this.name}さん`);
	}
}

person.callName();// => こんにちは 太郎さん

function fn(cb) {
	cb(); 
}
fn(person.callName);// => こんにちは グローバル太郎さん

グローバルオブジェクトに格納されているメソッド内のthis

メソッド内で呼び出す場合でも、setTimeoutような”windowオブジェクトに格納されているメソッド”内でthisを呼び出す場合においては、windowオブジェクトを参照するので注意が必要です。

window.name = "グローバル太郎";

const person = {
	name: '太郎',
	callName() {
		setTimeout(function () {
			console.log(`こんにちは ${this.name}さん`);// windowオブジェクトを参照する
		}, 1000);
		
	}
}

person.callName();// => こんにちは グローバル太郎さん

これはthisが直近で囲まれている(最初に見つかる)オブジェクトを基本的に参照するためです。

また、グローバルオブジェクトは省略が可能なので一般的には省略して記述しますが、正確には以下のように記述します。

window.setTimeout(function () {
	console.log(`こんにちは ${this.name}さん`);
}, 1000);

クラス内のthis

constructor内で呼び出す場合、thisはnew演算子でインスタンス化されたオブジェクト「instance」を参照します。
instance内にはclass Personが格納されています。

class Person {
	constructor(name) {
		this.name = name;
		console.log(this.name);
	}
}

const instance = new Person('太郎'); // 太郎

クラス内のメソッドで呼び出す際も同様でthisはインスタンス化されたオブジェクトを参照します。

class Person {
	constructor(name) {
		this.name = name;
	}

	callName() {
		console.log(this.name);
	}
}

const instance = new Person('太郎'); 
instance.callName();// => 太郎

bind によるthisの参照先の指定

単なる関数で this の参照先をオブジェクトにしたい場合はbind()と言うメソッドを使用する方法があります。

bind()を使用することによって、thisの参照先を意図的に変更することが可能です。

window.name = "グローバル太郎"; 

const person = {
	name: '太郎',
	callName() {
		console.log(`こんにちは ${this.name}さん`);
	}
}

const myFn = person.callName.bind(person);// bind()の第一引数に参照したいオブジェクトを指定
myFn();// => こんにちは 太郎さん

このようにオブジェクトメソッドを関数に定義する際にbindを呼び出し第一引数に参照したいオブジェクトを指定することで参照先を意図的に変更することができます。

また、setTimeoutなどのグローバルオブジェクトに格納されているメソッド内のthisもbindによる束縛は可能です。

const person = {
	name: '太郎',
	callName() {
		setTimeout(function () {
			console.log(`こんにちは ${this.name}さん`);
		}.bind(person), 1000);
		
	}
}
person.callName();// => こんにちは 太郎さん

bind, call, apply の違い

bind と類似したメソッドに call, apply があります。
どれも this の参照先を指定するものですが、

  • bindはメソッド使用時点では「関数の実行までは行わない」
  • call, applyは 使用と同時に「関数を実行する」

と言う違いがあります。

window.name = "グローバル太郎"; 

const person = {
	name: '太郎',
	callName() {
		console.log(`こんにちは ${this.name}さん`);// bind()によりpersonオブジェクトを参照する
	}
}

const myFn = person.callName.bind(person);
myFn();// => こんにちは 太郎さん

const myFn2 = person.callName.call(person);// => こんにちは 太郎さん
const myFn3 = person.callName.apply(person);// => こんにちは 太郎さん

他にもbind, call, applyには第二引数以下の値の返し方などの違いもありますがthisの参照の話からは脱線してしまうので割愛させていただきます。

bind, call, applyの引数の取り方の違いは以下のリポジトリ が参考になりました。

bind, call, applyの違い

アロー関数内のthis

今まで使ってきた通常の関数やメソッド内では this の値は確定しておらず、関数を呼び出した際にthis の値が決まりますが、
ES6から導入されたアロー関数では、宣言された時点で this の値を確定します。

また、スコープチェーンを辿って順番に外部スコープへ参照先を探しにいきます。

const person = {
	name: '太郎',
	callName() {
		setTimeout(() => {
			console.log(`こんにちは ${this.name}さん`);
		}, 1000);
		
	}
}
person.callName();// => こんにちは 太郎さん

このようにアロー関数を使用する場合はthisが参照するオブジェクトを確定させることができます。

まとめ

  • メソッド内のthisは基本的に直近のオブジェクトを参照する
  • 通常の関数、コールバック関数内のthisは基本的にグローバルオブジェクトを参照する。
  • グローバルオブジェクトに格納されているメソッド内のthisはグローバルオブジェクトを参照する。
  • bind, call, applyによりthisの参照先を指定できる
  • アロー関数では宣言された時点でthisの値が確定する

参考文献