弥生研究所

人は誰しもが生きることの専門家である

【Javascript】this について

Javascript の this について説明します。

私は、長らく Java を使ってきたので、Javascript の this の概念には戸惑いました。this を理解する手助けになればと思います。

基本(メソッドの呼び出し)

あるオブジェクトのメソッドを呼び出したとき、そのオブジェクトは「呼び出しコンテキスト」になります。そして、メソッドの中の this は「呼び出しコンテキスト」のオブジェクトになります。

function returnThis() {
  return this;
}

const rectangle = {
  width: 10,
  height: 20,
  returnThis,
};

console.log(rectangle.returnThis());
// rectangle のオブジェクト

ふーん、Java と同じじゃん。そう、同じに見えるでしょう。たしかに、呼び出しコンテキストの概念が Javascript における this の考え方の基本になります。

ただし、Javascript では、以下の点において this の振る舞いに変化があります。

  • メソッドは関数をオブジェクトのプロパティに代入した状態にすぎない
  • 関数を変数やオブジェクトのプロパティに代入できる
  • 関数を関数の中に定義できる
  • 文を関数以外の場所に記述できる
  • アロー関数

派生(グローバル)

関数の一部ではないトップレベルのコードでは、this はグローバルを参照します。

console.log(this);
// ブラウザの場合、Window オブジェクト

'use strict';
console.log(this);
// ブラウザの場合、Window オブジェクト

派生(関数の呼び出し)

呼び出しコンテキストを明示的に指定しないとき、つまり、オブジェクトのメソッドを呼び出すのではなく関数そのものを呼び出したときは、this は暗黙的にグローバルを参照します。

function returnThis() {
  return this;
}

console.log(returnThis());
// ブラウザの場合、Window オブジェクト
'use strict';
function returnThis() {
  return this;
}

console.log(returnThis());
// undefined

ただし、strict モードの場合は this は undefined になります。this は呼び出しコンテキストを指すという基本を考えれば、呼び出しコンテキストを指定しない関数呼び出しでは、this が undefined になるのは筋が通っています。

ところで、メソッドを変数に代入して関数として呼び出せば、this が指し示すものも変わります。

const rectangle = {
  width: 10,
  height: 20,
  returnThis() {
    return this;
  },
};

console.log(rectangle.returnThis());
// rectangle のオブジェクト

const foo = rectangle.returnThis;

console.log(foo());
// ブラウザの場合、Window オブジェクト

これが、Javascript において、this の扱いに混乱が生まれる理由の一つです。関数の中で this を書いた場合、this が何を参照するのかは、関数がどのように呼ばれるかによってきまります。

関数呼び出しでも、呼び出しコンテキストを指定することができます。それが call 関数です(apply もありますが話が逸れるので割愛します)。

function returnThis() {
  return this;
}

const rectangle = {
  width: 10,
  height: 20,
};

console.log(returnThis());
// ブラウザの場合、Window オブジェクト

console.log(returnThis.call(rectangle));
// rectangle のオブジェクト

さらに、bind 関数でも、呼び出しコンテキストを指定できます。

function returnThis() {
  return this;
}

const rectangle = {
  width: 10,
  height: 20,
};

console.log(returnThis());
// ブラウザの場合、Window オブジェクト

const boundReturnThis = returnThis.bind(rectangle);

console.log(boundReturnThis());
// rectangle のオブジェクト

気持ち悪いですねー(誉め言葉)。

派生(コンストラクタ)

new 演算子によって呼び出される関数をコンストラクタと呼びます。そして、new 演算子を使ってコンストラクタを呼び出すと、this はオブジェクト自身を指し示します。

function Rectangle() {
  console.log(this);
}

Rectangle();
// ブラウザの場合、Window オブジェクト

new Rectangle();
// Rectangle のオブジェクト(グローバルじゃない!)

派生(アロー関数)

さて、関数の中の this が何を参照するかは、関数の中では決まらず、関数の呼び方によって決まることが分かりました。this は呼び出しコンテキストによって決まるのです。

しかし、呼び出しコンテキストに影響されない this を記述する関数が存在します。それがアロー関数です。

const rectangle = {
  width: 10,
  height: 20,
  returnThis: () => this,
};

console.log(rectangle.returnThis());
// ブラウザの場合、Window オブジェクト(あれ?)

アロー関数の中で記述されている this は、呼び出しコンテキストではなく、アロー関数が定義されているスコープの this と同等です。上記の例では、アロー関数は変数 rectangle に代入するオブジェクトのプロパティとして定義されています。そのスコープは関数の一部ではないトップレベルのスコープなので、グローバルを参照しているのです。

コールバックの場合、この違いを理解しやすいです。

const rectangle = {
  width: 10,
  height: 20,
  logThis() {
    setTimeout(function callback() {
      console.log(this);
    }, 1000);
  },
};

rectangle.logThis();
// ブラウザの場合、Window オブジェクト
const rectangle = {
  width: 10,
  height: 20,
  logThis() {
    setTimeout(() => {
      console.log(this);
    }, 1000);
  },
};

rectangle.logThis();
// rectangle のオブジェクト

this をログ出力する無名のアロー関数が logThis() 関数の中で定義されているので、アロー関数の中の this は、logThis() 関数の呼び出しコンテキストと同等になります。

ちなみに、アロー関数は上位の this に依存するだけなので、上位の this が呼び出しコンテキストの違いによって変われば、アロー関数の中の this も変わります。

const rectangle = {
  width: 10,
  height: 20,
  logThis() {
    setTimeout(() => {
      console.log(this);
    }, 1000);
  },
};

const foo = rectangle.logThis;

foo();
// ブラウザの場合、Window オブジェクト

以上です。

f:id:yayoi-tech:20190616172928p:plain