🧐

アロー関数と通常の関数ではthisの挙動が違う【JavaScript】

2022/09/17に公開

なぜこの記事を書こうと思ったのか

アロー関数と通常の関数の違いはいくつかあると思うのですが、
「thisの挙動が違う」のは、大きな違いだと思います。
その違いが頭から抜けており、先日の業務中に意外と時間を浪費してしまったので、
今回記事に残して、少しでも自分の頭の中に定着させようと思いました!
また意外にこの問題にハマっている人もいるのではないかと思いますので、その人たちの助けになればいいなと思い記事を書きます!

この記事でわかること

  1. JavaScriptにおいて、アロー関数と通常の関数では、それぞれでthisの特性を持っていること。またその特性の違いから、挙動が違うことがわかる。

この記事では触れないこと

  1. アロー関数とは??
  2. this以外のアロー関数と通常の関数の違い

通常関数とアロー関数のthisの特性を確認する

まずは通常関数とアロー関数の違いを確認するために、それぞれの関数が持っているthisの特性について見てみましょう。それぞれの特性を知ることで、自然と違いを理解することができると思います。

通常関数のthis

まず通常関数のthisについてですが、thisで受け取る値は、「呼び出し元」 が基準になります。
呼び出し元(関数実行)の場所によって、thisがどんな値を受け取るのかが決まるので、呼び出し元をちゃんと見る必要があります。

下記の記事を参考に、通常関数のthisについてみていきたいと思います。
https://qiita.com/takeharu/items/9935ce476a17d6258e27

まず、記事によると通常の関数には、4種類のthisが存在するとのことです。

例1) メソッド呼び出しパターン

メソッドは、親となるオブジェクトをthisで受け取る機能を持っているので、下記のコードでの呼び出し元のthisは、定義したobjを受け取っています。なのでobj.valueの値を表示します。
親となるオブジェクトとは、自分自身を含めたオブジェクトになります。

// objを定義する
const obj = {
  value: 20,
  show: function() {
    console.log(this.value);
  }
}

// obj内で定義した関数を実行する
obj.show(); // => 20

例2) 関数呼び出しパターン

関数とメソッドの違いは下記の通りで、ドッドがあるかないかです。

obj.show(); // メソッド呼び出し
show(); // 関数呼び出し

関数呼び出しパターンでは、thisはグローバルオブジェクトを受け取ります。

// show関数を定義する
function show() {
  console.log(this)
}

// show関数を実行する
show(); // thisはグローバルオブジェクトを表示する

例えば下記のように、messageを定義すると、messageはグローバルな変数として、どこからでも呼び出すことができるように定義されます。

function show() {
  this.message = "グローバルオブジェクトです!"
  console.log(this.message)
}

show(); // グローバルオブジェクトです!

これを踏まえてメソッド呼び出しパターンと関数呼び出しパターンを組み合わせると結果は下記のようになります。

const obj = {
  value: 20,
  show: function() {
      // メソッドのthisは親となるオブジェクトの値を受け取る
    console.log(this.value); // 20
    
    // 関数のthisは、グローバルオブジェクトを受け取るので、存在しないvalueにアクセスしている
    function show() {
      console.log(this.value) // undefined
    }
    show();
  }
}

obj.show();

上記のコードでは、メソッド内で関数呼び出しが行われているために、undefinedになっていることがわかるかと思います。仮に関数呼び出しで、obj.valueにアクセスしたい時は下記のようにthisを別の変数名として持っておくことで関数呼び出しでもアクセスできるようになります。

const obj = {
  value: 20,
  show: function() {
    // thisを別の変数名として持つ
    const self = this;
    console.log(self.value); // 20
    
    function show() {
      console.log(self.value) // 20
    }
    show();
  }
}

obj.show();

例3) コンストラクタ呼び出しパターン

次にコンストラクタを呼び出した時のパターンについてみていきます。
下記の場合、thisが受け取る値は何になるのでしょうか??
答えは、newを使って生成したインスタンスが「this」にセットされます。
なので、「new Person('太郎', 25)」で生成したインスタンスのthisに「person」がセットされます。

// コンストラクタを定義する
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// new演算子を使ってインスタンスを生成する
const person = new Person('太郎', 25);

console.log(person.name); // 太郎
console.log(person.age); // 25

ちなみに。。。
コンストラクタとは、インスタンス(実体)作成時に初期化関数として呼ばれるメソッドの定義のことです。コンストラクタによってインスタンスを生成し、初期化も行います。

インスタンスの生成は、new演算子を使って、定義したコンストラクタを呼び出すことで実現します。
インスタンスの初期化は、インスタンス生成時にインスタンス変数に値をセットすることで実現します。
インスタンス変数とは、生成されたインスタンス内でのみ使える変数であり、上記のコードであれば、インスタンス生成時に渡す引数によって、インスタンスごとに使える変数の値が変わってきます。

今回は3つの例を使って通常関数のthisが受け取る値について確認しました。
最初に書いた通り、通常関数のthisは関数を定義した段階では決まっておらず、呼び出し元で関数が実行された時に初めて受け取る値が決めるため、 呼び出し元によってthisの受け取る値が変化することがわかったと思います。

アロー関数のthis

次にアロー関数のthisの特性についてみていきます。
アロー関数は、関数が宣言された時点で、thisが受け取る値が確定するのが特徴です。
通常関数とは違い、一度thisの値が確定すると以降も変わることなく、呼び出し元がどこなのかも関係がなくなります。

下記の記事を参考にしました。
https://qiita.com/mejileben/items/69e5facdb60781927929

まずはアロー関数のthisの特性を知るために、通常関数でコードを書きます。

const message = 'Hello World';

function setMessage(){
  console.log(this.message);
}

const obj = {
  message: 'Hello Japan'
  func: setMessage
}
const obj2 = {
  message: 'Hello USA'
  func: setMessage
}

obj.func(); // Hello Japan
obj2.func(); // Hello USA

ここまで読んでいただいた方は、もう理解できていると思いますが、これは先ほど書いた「メソッド呼び出しパターン」に当てはまりますね。

  1. setMessage()で関数を宣言したときthisの値はまだ決まっていない。
  2. 呼び出し元の関数が実行された時にthisの値が決まる。
  3. thisの値は、呼び出し元によって変わる。今回であれば呼び出し元の親のオブジェクトが持つmessageの値によって表示するものが変わる。

それでは本題のアロー関数で今のコードを書いて確認してみます。

const message = 'Hello World!!';

const setMessage = () => {
  console.log(this.message);
}

const obj = {
  message: 'Hello Japan'
  func: setMessage
}
const obj2 = {
  message: 'Hello USA'
  func: setMessage
}

obj.func(); // Hello World
obj2.func(); // Hello World

先ほどとは違い、どちらとも最初に宣言した変数messageの値を表示しました。
最初に書いた通り、アロー関数のthisは通常関数のthisとは違いアロー関数が宣言された時点でthisの値を確定させてしまいます。
setMessageというアロー関数は、宣言された時点でthisの値を決めるため、通常の関数とは違い呼び出し元がどこで呼ばれているかを気にする必要がありません。

最後に

ここまで読んでいただき、ありがとうございます!
今回は、通常の関数とアロー関数のthisの挙動の違いについて書きました。
最後に簡単におさらいしてみます。

  1. 通常の関数のthisは、関数が宣言された時点ではthisが受け取る値は決まっていない。関数が呼び出された場所で初めて受け取る値が決まり、その場所によって受け取る値が変わる。
  2. アロー関数のthisは、関数が宣言された時点で、thisの受け取る値が確定する。呼び出し元がどこなのかは関係なく、最初に確定してからは値が変わることもない。

色々な記事を読んでいると、もっと深いところまで書かれている記事もありました。
まだまだ理解は浅いですが、今回の記事を書くことで以前よりは理解が深まったと思います。
またこれを読んで、通常の関数とアロー関数のthisの挙動の違いについて、、少しでも理解が深まった方がいてくれると嬉しいです!!

Discussion