JavaScript

【JavaScript】アロー関数式と通常の関数の違い【thisに要注意】

今日は、ES6 で導入されたアロー関数と通常の関数(functionとかくやつ)の違いを紹介します。「アロー関数を使えば、すっきりと関数を記述できる!」というのは事実ですが、オブジェクトのメソッド内でのthisの取り扱いに注意する必要があるので、その辺りをまとめます。

アロー関数とは

まずはアロー関数の定義をさらっと確認しておきましょう。

  1. より短く記述できる通常の関数式の代替構文
  2. this、 super、 new.target を束縛しない
  3. アロー関数式はメソッドでない関数に最適

1は、そのまんまですが、2、3についてはこれから紹介する例を見れば、その意味することがわかりやすいかと思います。

オブジェクトのメソッド内での”this”取り扱い時の注意(うまくいかない例)

まずは、アロー関数なしで、thisの取り扱いに注意しなければならない例を紹介します。

ポイントは「メソッドの呼び出し元のオブジェクトが何であるかを確認する」ことです。callAgeメソッドの、setTimeout関数に注目して下さい。

var profile = {
  name: "Keisuke",
  age: 33,
  callAge: function() {
    // 実行時の呼び出し元オブジェクトはprofileなのでthis.ageにアクセス可能;
    console.log(this.age); // 33

    setTimeout(function() {
      // setTimeout() 実行時の呼び出し元オブジェクトはグローバルオブジェクト
      console.log("歳は" + this.age); // 歳はundefined
    }, 1000);
  },
  callName: function() {
    // 実行時の呼び出し元オブジェクトはprofileなのでthis.nameにアクセス可能;
    console.log("私の名前は" + this.name); // 私の名前はKeisuke
  }
};

profile.callAge();
profile.callName();

 

この例では、setTimeout関数にthisをbindしていないので ”歳はundefined” と出力されています。なぜそうなるかは、以下のMDNの解説の通りです。

setTimeout() によって実行されるコードは、setTimeout が呼び出された関数とは別の実行コンテキスト内から呼び出されます。呼び出された関数に this キーワードを設定する通常の規則を適用して、呼び出しあるいは bind で this を設定しなければ、非 strict モードでは global (または window)、strict モードでは undefined になります。これは、setTimeout が呼び出された関数の this 値と同じにはなりません。

出典:MDN web doc ’this’問題

ES6前までは、このような時には、次の例のようにbind関数を指定していました。

アロー関数の導入以前は、以下のように”this”にアクセス

【方法1】”this” をbindする

setTimeout関数にcallAgeメソッドのthis、つまり profile を bind してあげると、うまくいきます。

var profile = {
  name: "Keisuke",
  age: 33,
  callAge: function() {
    setTimeout(
      function() {
        // callAgeメソッドのthis、つまり profile を bind してあげる
        console.log("歳は" + this.age); // 歳は33
      }.bind(this),
      1000
    );
  },
  callName: function() {
    console.log("私の名前は" + this.name); // 私の名前はKeisuke
  }
};

profile.callAge();
profile.callName();

【方法2】”this”の値をアクセス可能な別の値に割り当てる

こちらもシンプル。setTimeoutよりも一つ上のスコープ内なら、thisにアクセス可能なので、そのスコープでthisthatに割り当て、that.ageという形で、ageにアクセス。

var profile = {
  name: "Keisuke",
  age: 33,
  callAge: function() {
    // アクセス可能なスコープ内で、thisをthatに割り当てる
    var that = this;

    setTimeout(function() {
      // thisではなくて、thatからageの値にアクセス
      console.log("歳は" + that.age); // 歳は33
    }, 1000);
  },
  callName: function() {
    console.log("私の名前は" + this.name); // 私の名前はKeisuke
  }
};

profile.callAge();
profile.callName();

setTimeoutに、アロー関数を使用します

ここでアロー関数の出番です。setTimeoutに通常の関数ではなく、代わりにアロー関数を使用します。アロー関数はthisを束縛しない(アロー関数自身はthisを持たない)ので、setTimeout関数を取り囲むcallAge関数のthis、つまりprofileが入ります。

var profile = {
  name: "Keisuke",
  age: 33,
  callAge: function() {
    setTimeout(() => {
      // アロー関数を使うことにより、callAgeメソッドの”this”、つまり”this”はちゃんとprofileオブジェクトを参照する
      console.log("歳は" + this.age); // 歳は33
    }, 1000);
  },
  callName: function() {
    console.log("私の名前は" + this.name); // Keisuke
  }
};

profile.callAge();
profile.callName();
 

注意!callNameにもアロー関数を使ってみると上手くいきません

ここで、「callNameメソッドにもアロー関数を使えば、すっきりと記述できていいじゃん!」と思うかもしれませんが、それはうまくいきません。

var profile = {
  name: "Keisuke",
  age: 33,
  callAge: function() {
    setTimeout(() => {
      console.log("歳は" + this.age); // 歳は33
    }, 1000);
  },
  callName: () => {
    // アロー関数は自身で"this"を持たないので, this.name は undefinedとなる。
    // これが、アロー関数式はメソッドでない関数に最適であると言われる理由の一つ。
    console.log("私の名前は" + this.name); // 私の名前は undefined
  }
};

profile.callAge();
profile.callName();

まとめ:アロー関数式と通常の関数の違い

最後にもう一度、アロー関数の定義をみておきましょう。

  1. より短く記述できる通常の関数式の代替構文
  2. this、 super、 new.target を束縛しない
  3. アロー関数式はメソッドでない関数に最適

この記事では、super、 new.targetには触れていませんが、前述の例のように、2と3は関連しています。アロー関数はメソッドには向かない理由、通常の関数式と違いthisを束縛しないという特徴を覚えておきましょう!

ABOUT ME
Wata
30歳で営業職からエンジニアに転職した者のブログです。 大手海運業→総合商社→ソフトウエアエンジン。現在は、オーストラリアにてエンジニアとして働いています。 未経験からのエンジニアへの転職、フロントエンド周りの技術、エンジニアの仕事環境、趣味の旅行、JAL修行、オーストラリアの情報などを発信しています!

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です