今回は「アロー関数」(Arrow functions) と「this」のお話です。
次にような方にとっては勉強・復習になるかと思います。
アロー関数知ってるけど、普通の関数とどう違うの?
アロー関数を使うときに気をつけるべき点ってある?
アロー関数が用意される前までは「function」キーワードを使って関数を定義する選択肢しかありませんでした。
既に関数キーワードがあるのになぜ、アロー関数が用意されたのでしょうか?
アロー関数が生まれた理由も含めて、今回の記事では次の内容について話していきます。
- アロー関数が生まれた理由
- 実例からみるthisの挙動
それでは、1つ目の「アロー関数が生まれた理由」から解説します。
目次
アロー関数が生まれた理由
既に「function」キーワードが存在しているのにも関わらず、なぜアロー関数が用意されたのでしょうか?
それは大きく次の2点の理由からです。
- 関数を短く書きたい
- this を束縛したくない
関数を短く書きたい
1つ目の理由はそのまんまですね。実際に文字数がどれくらい短くなったか比べてみましょう。
文字数の比較
関数の種類 | 文法 | 文字数 |
通常の関数 | function(){} | 12文字 |
アロー関数 | ()=>{} | 6文字 |
上記の表からわかるように、アロー関数を使うことで文字数が従来の書き方と比べて半分に減りました。
たったの6文字と感じるかもしれませんが、実際には開発中に関数の定義をする場面は何度もあります。地味ではありますが、タイピング数が少なくなるのは嬉しいことです。
this を束縛したくない
1つ目の理由も嬉しいことですが、アロー関数が一番力を発揮するのは「this」の取り扱い方です。
JavaScriptの「this」の挙動はクセが強くて、必ず初心者がつまづくポイントでもあります。
JavaScriptの「this」の挙動は言葉だけで理解するのは難しいので実際にサンプルコードを使って説明します。
なお「this」の挙動をわかりやすくするために「class」を使って説明しています。
classについて、あまり理解していない方は「JavaScriptでクラスを作ろう! class構文の使い方徹底解説」が参考になるかと思います。
それでは早速コードを見ていきましょう。
通常の関数を使った場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <p id="counter">0</p> <button id="button">+1</button> <script src="./main.js"></script> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Counter { constructor() { this.count = 0; this.targetElement = document.getElementById('counter'); this.button = document.getElementById('button'); // 通常の関数を使ったとき意図しない挙動になる button.addEventListener('click', function(event){ // 「this」の内容はCounterのインスタンスではなく、buttonのDOMとなっている // 「this === button」が「true」から、「this」が「button」であることがわかる。 console.log('thisの内容 : ', this); console.log('thisはbuttonを指している?', this === button); // targetElementがNaNと表示されるまでの流れ // 1. this.countのthisはCounterインスタンスを期待しているが、実際にはbuttonである // 2. ここでは this.counter は button.counterという意味になる // 3. button.counterは存在しないためundefinedとなる // 4. undefined + 1 => NaNとなる // 5. targetElementにはNaNがセットされる this.count++; targetElement.innerHTML = this.count; }); } } new Counter(); |
実行結果は次のとおりです。
実行結果の画面右側がコンソールの出力、左側がHTMLの表示部分になります。
左側のHTML表示部分の数字の「0」が「NaN」という文字に置き換わっているのがわかるかと思います。
JavaScriptのコード上にもコメントに書いていますが、NaNと表示されるまでの流れは次のとおりです。
- ボタンがクリックされた時に呼ばれるコールバック関数のthisを11, 12行目で確認するとthisはボタン(= button)自身を指している
- 20行目の「this.count」は「button.count」という意味になる
- this.count(= button.count)は定義されていないので「undefined」となる
- undefinedのインクリメント(undefined++)はNaNとなる
- 20行目でthis.count(= button.count)にはNaNがセットされる
- 21行目でtargetElementのinnerHTML(HTML表示部分)にthis.count(= NaN)がセットされる
- HTMLの「0」と表示されていたところが「NaN」に置き換わる
アロー関数を使った場合
それでは次にアロー関数をつかった場合を見ていきましょう。
HTMLは同じものを使うのでJavaScriptのサンプルコードだけ書いていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Counter { constructor() { this.count = 0; const targetElement = document.getElementById('counter'); const button = document.getElementById('button'); button.addEventListener('click', (event) => { // 「this」の内容はCounterのインスタンスを指している // 「this === button」が「false」から、「this」が「button」でないことがわかる。 console.log('thisの内容 : ', this); console.log('thisはbuttonを指している?', this === button); this.count++; targetElement.innerHTML = this.count; }); } } new Counter(); |
実行結果は次のとおりです。
通常の関数(= functionキーワード)を使ったときと挙動が変わって、正しく数字がインクリメントされているのがわかります。
上の実行結果画像の右半分を確認してみると、「thisの内容 : Counter {count : 0}」のように出力されているのがわかるかと思います。
これは、「this」がCounterインスタンスであることを表しています。
インスタンスとはクラスを使ったときの概念で、ここではオブジェクトと同じ意味だと認識していただいて大丈夫です。
例: 「const counter = {counter : 0}」・・・counterオブジェクト
通常関数を使ったときと、アロー関数を使ったときの「this」の内容について改めてまとめてみましょう。
buttonをクリックしたときのthisの内容
関数の種類 | thisの内容 |
通常の関数 | buttonのDOM |
アロー関数 | Counterインスタンス |
上記の表から「通常の関数」を使ったときは、クリックしたときのHTMLの要素(今回の場合だとbutton)がthisになることがわかります。
「アロー関数」を使った場合はクリックしたHTMLの要素にならず、処理が実装されているオブジェクト(今回の場合はCounterインスタンス)がthisになることがわかります。
「処理が実装されているオブジェクト」と言葉で説明するだけでは難しいと思うので、その他のコールバック関数の使用例を見て理解していきましょう。
実例からみるthisの挙動
実例ではJQueryを使っていきます。
今回はDOMの読み込みが完了したときに呼ばれる「$(function() {})」を省略しないで「$(document).ready(function(){})」を使って説明します。
明示的に「document」を使っているというのを理解することで、「this」の内容が「document」になるのか、それ以外のものになるのかわかりやすくなります。
それでは次のサンプルコードを見ていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="./main.js"></script> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// DOM読み込み後に実行されるコード // 通常の関数を使った場合 $(document).ready(function(){ console.log('START ---通常関数の処理--------------------'); console.log('通常関数のthisの内容 : ', this); console.log('通常関数のthisはdocumentと同じ? : ', this === document); console.log('END ---通常関数の処理--------------------'); }); // アロー関数を使った場合 $(document).ready(() => { console.log('START ---アロー関数の処理--------------------'); console.log('アロー関数のthisの内容 : ', this); console.log('アロー関数のthisはdocumentと同じ? : ', this === document); console.log('END ---アロー関数の処理--------------------'); }); |
実行結果は次のとおりです。
実行結果の画像の出力内容を見ると、通常の関数を使ったときとアロー関数を使ったときの「this」の内容は次のとおりです。
DOM読み込み完了時のthisの内容
関数の種類 | thisの内容 |
通常の関数 | documentオブジェクト |
アロー関数 | Windowオブジェクト |
1つ前の章の「this を束縛したくない」で説明したボタンクリック時のそれぞれの関数を使ったときの「this」の内容と比較してみましょう。
buttonをクリックしたときのthisの内容
関数の種類 | thisの内容 |
通常の関数 | buttonのDOM |
アロー関数 | Counterインスタンス |
通常の関数の場合は、クリックやDOM読み込み完了時などのイベントが実行されるとき、「this」の内容はイベントの本体になります。
イベントの本体とは今回の場合だと次のようになります。
- ボタンクリック時 => ボタンがイベントの本体
- DOM読み込み完了時 => documentがイベントの本体
「イベント」についてよくわからない方は、次の記事で詳しく解説しているのでそちらを参考にしていただけたらと思います。
アロー関数を使った場合、「this」の内容は「処理が実装されているオブジェクト(場所)がthis」と説明しました。
その上で、ボタンクリック時とDOM読み込み完了時の処理が実装されているオブジェクト(場所)をまとめると次のようになります。
- ボタンクリック時 => Counterクラスのメソッド内(= Counterインスタンス)
- DOM読み込み完了時 => グローバル空間(= Windowオブジェクト)
関数内やオブジェクト内ではなく、ファイル直下にコードを記述した場合、JavaScriptだとそのコードはグローバルオブジェクト(= グローバルスコープ)に所属することになります。
ブラウザのグローバルオブジェクトは「windowオブジェクト」となるため、画像内のアロー関数の出力結果の内容を見ると「Window」となっていることがわかります。
「グローバルオブジェクト」については以下の記事が参考になるかと思います。
まとめ
JavaScriptのthisはクセが強くて初心者にとって理解するハードルが高い部分でもあります。
通常の関数を使ったときと、アロー関数を使ったときで「this」の挙動が変わるということをここまでサンプルコードを交えて説明してきました。
もう一度ここで通常関数とアロー関数を使ったときの「this」の違いを確認してみましょう。
関数の種類 | thisの内容 | 例 |
通常の関数 | イベントの本体 |
|
アロー関数 | 処理の実装場所 |
|
JavaScriptの「this」を理解して、使いこなせるようにことは脱初心者の1つのステップにもなるので、今回の記事が「this」を理解するきっかけになったら嬉しいです^^
もし「もう少し詳しく教えて欲しい」などありましたら、以下よりご連絡をいただけたらと思います^^