⚽️

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

に公開
1

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

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

この記事でわかること

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

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

  • アロー関数とは??
  • 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(); // undefined
obj2.func(); // undefined

先ほどの通常関数では Hello Japan、Hello USA と表示されましたが、アロー関数では どちらも undefined になりました。

最初に書いた通り、アロー関数の this は通常関数の this とは違いアロー関数が宣言された時点で this の値を確定させてしまいます。

今回の場合、setMessage はグローバルスコープで定義されているため、this はグローバルオブジェクトを指します。そして const で宣言した変数はグローバルオブジェクトのプロパティにならないため、this.message は undefined となります。

setMessage というアロー関数は、宣言された時点で this の値を決めるため、通常の関数とは違い呼び出し元がどこで呼ばれているかを気にする必要がありません。

最後に

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

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

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

GitHubで編集を提案

Discussion

アルアル

アロー関数の例のthis
試しましたか?

undefinedになりますよ