JavaScriptのthisは、他の変数とは異なり少し特殊な動作をします。
そのため、JavaScriptを学習中の人にとってはthisの概念を理解するのは容易ではなく、なんとなくthisを使っていたり、何のためにthisが使われているのか理解するのに時間がかかることがあります。

そこで今回は、thisの参照先やどのような用途で使われるのか具体的に解説していきます。

this

thisとは、関数の中で使われる読み取り専用のキーワードです。thisの値は、基本的に関数が実行される際に確定します。
そのため、関数がどのように呼び出されたかによってthisの値が決まり、関数が呼ばれる度に値が異なることがあります。

このように特殊な性質を持つことから、JavaScriptでは便利な変数として利用されています。
一方、言葉だけでは理解しにくいため、thisがどのように動作をするのか用途ごとに見ていくと分かりやすいです。

thisの振る舞い

ここからは、thisの参照先がどのように決まるのか、解説していきます。

グローバルコンテキスト

グローバルコンテキストとは、ファイルのトップレベル(もっとも外側のスコープ)に書かれたコードのことで、トップレベルにあるthisは、グローバルオブジェクトを参照します。

グローバルオブジェクトは環境ごとに異なりますが、ブラウザで実行する場合はwindowオブジェクトとなります。

console.log(this); // Window
console.log(this === window); // true

this.hello = 'Hello';
console.log(window.hello); // Hello

関数コンテキスト

関数コンテキストとは、関数の内側に書かれたコードのことを指します。
関数の内側にあるthisは、通常、関数の呼び出し時に値が決まるという特徴があります。

非strictモードとstrictモード

strictモードが設定されていない場合、関数内のthisはグローバルオブジェクトを参照します。
つまり、トップレベルにあるthisと同じ結果となります。

function test() {
  console.log(this); // Window
  console.log(this === window); // true
}
test();

一方、strictモードが設定されている場合、実行コンテキスト内でthisの値の指定がなければ、以下のようにundefinedとなります。

function test() {
  'use strict';
  console.log(this); // undefined
  console.log(this === window); // false
}
test();

関数宣言と関数式

上記では、strictモードの有無によってthisの値が異なることが分かりました。
では、関数の種類によってthisの参照先に違いが出るのでしょうか。
関数宣言と関数式によるthisの動きを見てみましょう。

以下では、関数宣言のtest1と関数式のtest2が定義されています。

// 非strictモード
function test1() {
  console.log(this);
}
test1();
// Window

const test2 = function() {
  console.log(this);
}
test2();
// Window

// strictモード
'use strict';

function test1() {
  console.log(this);
}
test1();
// undefined

const test2 = function() {
  console.log(this);
}
test2();
// undefined

どちらも同じ結果となります。

これらは、実行コンテキスト内で値の指定がない(ベースオブジェクトがない)ため、thisの値がundefined、またはwindowオブジェクトという結果になっています。
そのため、メソッドを使わない単純な関数内でthisを使うメリットはあまりありません。

次のメソッドの項目を見てみるとより違いが分かります。

メソッド

JavaScriptでは、オブジェクトのプロパティとして定義されている関数のことをメソッドと呼びます。
そのため、メソッド内のthisは、自身のオブジェクトを参照することになります。
言葉だけだと理解しにくいかもしれませんが、以下の実行結果を見るとどうでしょうか。

const test = {
  id: 1,
  name: '田中太郎',
  user: function() {
    console.log(this);
  }
};
test.user();
// {id: 1, name: '田中太郎', user: ƒ}

testという名前のオブジェクト内に定義した関数user(これをメソッドと呼ぶ)では、その中にあるthisをコンソールで出力しています。
そして、メソッド内にあるthisがtestを参照しているという結果となっています。

コンストラクタ

コンストラクタは、thisが使われる代表的なケースです。
コンストラクタ内のthisは、new演算子を使って作成されたインスタンス(新しいオブジェクト)と関連付けられます。
この時、「this.○○」と記述することで、インスタンスのプロパティを定義することができます。

function Test(id, name) {
  this.id = id;
  this.name = name;
}
const user = new Test(1, '田中太郎');
console.log(user);
// Test{id: 1, name: '田中太郎'}

コンストラクタ内のthisがインスタンスのプロパティを参照していることが分かります。

アロー関数

アロー関数におけるthisの振る舞いは特殊なため、通常の関数との違いにも少し触れておきます。

通常の関数の場合、関数の呼び出し時によってthisの値が決まり、どのように呼び出されたかによってthisの値が異なると説明しました。
一方で、アロー関数内のthisの場合は、関数が定義された時点で値が決まります。

以下の例を見比べてください。

通常の関数

this.val = 'global';

function test1() {
  console.log(this.val);
}

const obj = {
  val: 'object',
  func: test1
};
obj.func();
// object

アロー関数

this.val = 'global';

const test2 = () => {
  console.log(this.val);
};

const obj = {
  val: 'object',
  func: test2
};
obj.func();
// global

いかがでしょうか。

アロー関数内のthisは、呼び出し元に関わらず、アロー関数の外側で定義されたthisの値が参照されています。
このような振る舞いをすることから、thisの値を束縛するという表現がされることがあります。
thisの値が固定化されているため、より直感的です。

まとめ

今回は、JavaScriptにおけるthisの振る舞いを見ていきました。

thisは、関数の種類や用途によって値が変わる特殊なキーワードです。
意図しない挙動を発生させないためにも、thisの振る舞いや参照先を理解しておくことは大切です。

thisの挙動を一度で理解するのは難しいため、使いながら少しずつ覚えていきましょう。

合わせて読みたいthisシリーズ

第1回:thisの振る舞い(当記事)
第2回:thisの参照 -call・apply
第3回:thisの束縛 -bind