JavaScriptのthisキーワードについて

2021年1月18日JavaScript

JavaScriptのthisについてまとめました。

JavaScriptのthisについて

JavaScriptのthisは、特別な変数ですべての関数実行コンテキストで作成されます。
thisとは、呼び出し元のオブジェクトへの参照を保持するキーワードのことです。
thisはJavaScriptエンジンによって準備されているキーワードで、グローバルコンテキストでも使用できますが、主に関数コンテキスト内で使用されます。

thisの値は静的(static)ではなく動的です。関数がどのように呼び出されたか参照先で変化します。
thisの値は実際に関数が呼び出されたときに値を割り当てられます。

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

オブジェクトの中の関数(メソッド)で呼び出し元を指したいときに、thisを使います。下の例では、yamada.yearと記述するのではなく、this.yearと記述するということです。


const yamada = {
  name: "yamada",
  year: 2000,
  calcAge: function () {
    return 2100 - this.year;
  },
};

console.log(yamada.calcAge()); //100

calcAge()メソッドの中にあるthisは、レキシカルスコープをたどって呼び出し元のyamadaを指します。そのため、this.yearはyamada.yearと同じ意味になり、値は1900となります。
このように、オブジェクトのメソッドとして実行される場合は、thisは呼び出し元のオブジェクトを指します。

関数のthis

関数として実行される場合、thisは、グローバルオブジェクトを参照します。つまりthisの指し示す先を指定していない場合、thisはundefinedになります。
ただし、これは、strictモードを使っていないとでません。

window.year = 2050;

const yamada = {
  name: "yamada",
  year: 2000,
  calcAge: function () {
    return 2100 - this.year;
  },
};

console.log(yamada.calcAge()); //100

const ref = yamada.calcAge;
console.log(ref()); //50

上の例では、yamadaというオブジェクトを介してcalcAge()をメソッドとして呼び出しておらず、関数として直接呼び出しています。
このような場合、thisはグローバルオブジェクトを参照します。上の例ではwindowオブジェクトに設定していますが、通常は設定していない場合が多いのでthisはundefinedになります。

コールバック関数のthis

window.year = 2080;

const yamada = {
  name: "yamada",
  year: 2000,
  calcAge: function () {
    console.log(2100 - this.year);
  },
};

function fn(ref) {
  ref();
}
fn(yamada.calcAge);//20
//this.yearはwindow.year(2080)であり、yamada.year(2000)ではない

コールバック関数でthisのあるオブジェクトのメソッドを呼び出したととしても、thisはグローバルコンテキストを参照します。引数に関数を渡すことは、メソッドを他の変数に代入していることと同じです。refにはcalcAge()の参照先がコピーされて、ref()ではそのコピーの参照先を直接呼び出しているため、関数を実行していることと同じになります。オブジェクト経由でメソッドを呼び出しているわけではない点に注意しましょう。
コールバック関数の使い方は次の記事を参考にしてみてください。


this.yearをwindow.year(2080)ではなく、yamada.year(2000)にしたい場合、次のようにbind()を使います。

bind()メソッドとthis

bind()メソッドはthisの取りうる参照先を意図的に変更することができます。
上の例を利用すると、

fn(yamada.calcAge); //20
fn(yamada.calcAge.bind(yamada)); //100

このようにbind()メソッドの第一引数として指定したオブジェクトをthisの参照先にすることができます。
calcAgeのfunctionとは別に新たなfunction’としてメモリ空間に追加され、それのthisが固定されています。
bind()によってthisが固定された新たな関数がメモリ空間に生成されるという点はとても重要なので頭の片隅に置いておきましょう。
bind()の使い方は次の記事を参考にしてみてください。

setTimeout()のthis

class MyObj {
  constructor() {
    this.first_name = 'yamada';
    this.last_name = 'taro';
  }

  printFullName() {
    setTimeout(function () {//window.setTimeoutなので、thisはwindow
      console.log(`Hi ${this.first_name}`);
    }, 1000);
  }
}

const obj2 = new MyObj();
obj2.printFullName();
// Hi undefined

setTimeoutはwindowオブジェクトのメソッドのため、正式にはwindow.setTimeout()となります。
thisは、オブジェクトが見つかるまでさかのぼります。
setTimeoutの中のthisが最初に見つけるのは、windowオブジェクトのため、thisの参照先はwindowになります。
これをclassのオブジェクトに変更するためには、bindを使います。

class MyObj {
  constructor() {
    this.first_name = 'yamada';
    this.last_name = 'taro';
  }

  printFullName() {
    setTimeout(function () {//window.setTimeoutなので、thisはwindow
      console.log(`Hi ${this.first_name}`);
    }.bind(this), 1000);
  }
}

const obj2 = new MyObj();
obj2.printFullName();
// Hi yamada

setTimeout()の中のコールバック関数にbind()を続けます。thisとしたのは、setTimeoutがある空間のthisの参照先はMyObjだからです。
setTimeout()の使い方は、次の記事を参考にしてみてください。

アロー関数のthis

アロー関数は、自身でthisを取得しません。
そのため、アロー関数のthisは、そのアロー関数の外側にあるレキシカルスコープのthisとなります。
次の例をみてみましょう。

const saito = {
  firstName: 'saito',
  year: 1900,
  calcAge: function () {
    console.log(this); // saitoのオブジェクト{}が返される。
    return 2200 - this.year; //thisはsaito、yearはsaitoのyearになる
  },
//アロー関数のthis
  greet: () => console.log(`こんにちは、 ${this.firstName}`);
}
saito.greet();//こんにちは、undefined

アロー関数はthisをとらないため、レキシカルスコープをたどります。greet()のレキシカルスコープはグローバルスコープでありwindowオブジェクトになります。そこにthisは指定されていないのでundefinedになります。
そのため、上のsaito.greet();の結果は、「こんにちは、undefined」が返されます。
もしグローバルスコープでfirstNameが定義されていれば、そのfirstNameの値が取得されてしまうのでバグになります。

ちなみに、const saito = {}の{}はブロックスコープではありません。
これはオブジェクトの{}です。

このようなことが起こるため、オブジェクトのメソッドとして、アロー関数は使用しない方がいいでしょう。

イベントリスナーのthis

イベントリスナーのthisは、ハンドラーがつけるDOM要素になります。


document.addEventListener('DOMContentLoaded', function () {
  const btn = document.querySelector('#btn');
  const hello = new HelloClass();
  hello.animate(); //Hello yamada taro
  btn.addEventListener('click', hello.log);
// Hello (thisがbtnになってしまっていて正しく動作しない)
});

class HelloClass {
  constructor(el) {
    this.name = 'yamada taro';
  }

  log() {
    console.log(`Hello ${this.name}`);
  }
}

上の例なら、thisはbtnになります。hello.logは関数として渡されているため、btnが最初に見つかるオブジェクトになります。
正しく動作させるためには、次の2つの方法があります。
bind()を使うか、コールバック関数でhello.logを関数(hello.log)としてではなく、メソッド(hello.log())として実行するかです。

document.addEventListener('DOMContentLoaded', function () {
  const btn = document.querySelector('#btn');
  const hello = new HelloClass();
  hello.log(); //Hello yamada taro
//   bind()をつかった例
btn.addEventListener('click', hello.log.bind(hello));
//Hello yamada taro

// 無名関数の中でメソッドとして実行する例
btn.addEventListener('click', function () {
  hello.log();
});
//Hello yamada taro
});

class HelloClass {
  constructor(el) {
    this.name = 'yamada taro';
  }

  log() {
    console.log(`Hello ${this.name}`);
  }
}

関数として渡す場合と、メソッドとして実行する場合でthisの挙動が変わる点に注意しましょう。
関数として渡すのであれば、bind()でthisを調整する必要があります。
メソッドとして渡す場合、無名関数に入れる必要があります。
addEventListenerの使い方は、次の記事を参考にしてみてください。

JavaScriptのthisの具体例


console.log(this);// windowオブジェクトを得られる

//シンプルな関数の呼び出しのthis
const calcAge = function (a) {
  console.log('aaaa');
  console.log(this); //シンプルな関数の呼び出しのthisなのでundefinedが返ってくる
}
calcAge(2000);;

//アロー関数のthis
const calcAgeArrow = a => {
  console.log('aaaa');
  console.log(this); //アロー関数のthisなのでレキシカルスコープをたどって一番上のthis,つまりwindowオブジェクトが返ってくる
}
calcAge(2000);;

//メソッドのthis
const sakto = {
  name: 'sakto',
  year: 1900,
  calcAge: function () {
    console.log(this); // saktoのオブジェクト{}が返される。
    return 2200 - this.year; //thisはsaito、yearはsaitoのyearになる
  }
}
sakto.calcAge(); //300

//メソッドのthisをコピーする

const takagi = {
  year: 2100,
};

takagi.calcAge = sakto.calcAge;
takagi.calcAge(); //100,thisはtakagiオブジェクトを指す。

const f = sakto.calcAge; //さらにコピーする
f(); //yearがundefinedと返される。fでyearを宣言していないから。