JavaScript
Web
es6

【JavaScript】アロー関数式を学ぶついでにthisも復習する話

対象読者

  • ES6を詳しくは知らない
  • なんとなくJavaScriptを書けるけど、JSのthisの特性についてまだよく知らない
  • アロー関数式を知らない、または知っているけど実装経験がない

概要

はじめまして。@mejilebenです。
新卒1年目のWebエンジニアとして働いています。あと数日で2年目に突入します。
今回Qiita初投稿です!

JavaScriptのES6で導入されたアロー関数式を、関数のただのシンタックスシュガーに過ぎないと思っていたのですが、調べてみると実際そうではないようだったので、thisの話と一緒にまとめてみました。

アロー関数式とは

ES2015(ES6)から利用可能になった新しいJavaScriptの構文の一つです。
ES6とは、2015年に標準として策定されたJavaScriptの新しい文法です。
ES6では他にも、letによる変数宣言が可能になったり、promiseという非同期処理の実装ができるようになったりと、アロー関数式以外にも様々な構文追加がありました。

さて、この記事の主題はそのなかでもアロー関数なのですが、実際にES6以前の関数と、アロー関数式を利用したソースコードを比較してみるとこんな感じです。

今までの関数とアロー関数式
let normalFunc = function(x){
    console.log(x);
}

let arrowFunc = (y) => {
    console.log(y);
}

normalFunc('今までの関数');
arrowFunc('アロー関数式');

結果として、
今までの関数
アロー関数式
の2行が出力されます。

また、関数の中身が1文のみ(例:return文だけ)といった場合は中括弧を省略できちゃいます。

アロー関数の省略形
let arrowFunc = (y) => console.log(y);

ということで、アロー関数式を使えば、「function」を書かなくて済むため、今までより短く関数を記述することができます。
アロー関数式と名前がついてはいるものの、書き方が違うだけで、挙動は今までの関数と同じように見えます。

”アロー関数はthisの値を語彙的に束縛する”らしい

しかし、実はアロー関数で関数を記述すると文字数が短くなるだけじゃなかったんです。
MDNを見にいってみるとこのような記述がありました。

アロー関数式 は、function 式 と比べてより短い構文を持ち、this の値を語彙的に束縛します (ただし、自身の this や arguments, super, new.target は束縛しません)。

より短い構文を持っているのは見たらわかるけど、”thisの値を語彙的に束縛する”ってなんでしょう・・・?

それを知るためには、まずはJavaScriptにおけるthisの扱いについて説明しなければなりません。

一般的にJavaScriptでは、関数の中でthisを使うと、その呼び出し元のオブジェクトになります。

関数内でのthis
param = 'global param';

function printParam(){
  console.log(this.param);
}

let object = {
  param: 'object param',
  func: printParam
}
let object2 = {
  param: 'object2 param',
  func: printParam
}

object.func();
object2.func();

上記のコードの実行結果は、

object param
object2 param

となります。
つまり、printParam関数内で利用しているthis.paramの値は、呼び出し元がobjectなのかobject2なのかで異なることになります。thisの値が関数を呼び出すときに決定されていて、関数を定義した段階ではthisは何者なのか決まっていないことがわかります。
誰に呼び出されるかで態度をコロコロ変えるなんて、なかなか信用出来ない奴ですね笑
では、このprintParam関数を、アロー関数式を使って定義してみましょう。

アロー関数で定義されたthis
param = 'global param';

let printParam = () => {
  console.log(this.param);
}

let object = {
  param: 'object param',
  func: printParam
}
let object2 = {
  param: 'object2 param',
  func: printParam
}

object.func();
object2.func();

実行してみると、結果は

global param
global param

と、なんと2つともglobal paramとなりました。
これは、コードの最初で宣言している変数paramの値です。
結論を言いますと、アロー関数式で宣言された関数は、宣言された時点で、thisを確定(=束縛)させてしまうのです。

補足:グローバルオブジェクトについて
// JavaScriptでvarやletを付けずに宣言された変数はグローバルオブジェクトというオブジェクトのプロパティになる。
param = 'global param';
// 呼び出し元のオブジェクトが存在しない状況ではthisはグローバルオブジェクトを示す
// printParam関数内のthisもグローバルオブジェクトになる
console.log(this.param);

したがって、アロー関数式で宣言された関数printParamは、宣言された時点でのthis.param、すなわち文字列「global param」をconsole.logする関数だと確定するのです。
呼び出し元がobjectだろうがobject2だろうが関係ありません。初心貫徹で、一貫してthisを束縛し続けるようになるのです。一途な子ですね。

語彙的に束縛する、という日本語訳がちょっと微妙だとは思いますが笑、つまりアロー関数式で宣言された関数は、宣言された時点でのthisを束縛して、呼び出し元のオブジェクトにかかわらず不変のものにするということです。

アロー関数式は、ただ既存の文法をより短くしたものではなく、挙動が異なるものだったのです。

アロー関数式を使う場面(個人的見解)

アロー関数式を使うべき場面としての個人的な見解は、ES6以前の書き方で、関数にbind(this)していた場面です。

例えば下記のようなコードを実行したい場合。

var person = {
  name: 'mejileben',
  hobby: 'programming',
  callHobbyLater: function(){
    setTimeout(function(){
      console.log('趣味は' + this.hobby);
    },1000);
  },
  callName: function(){
    console.log("私の名前は" + this.name);
  }
}

person.callHobbyLater();
person.callName();

このコードは、personオブジェクトのcallNameメソッドで名前を紹介し、callHobbyLaterメソッドで趣味を話すのですが、callHobbyLaterメソッドはsetTimeout関数によって、紹介を1000ミリ秒遅らせるように設定しています。
名前より前に趣味を紹介する人なんていませんからね。当然の礼儀です。

さて、このコードを実行してみると、思わぬ結果となります。

私の名前はmejileben
趣味はundefined

趣味が定義されていないことになるのです。setTimeout関数に渡された関数は、実行時の呼び出し元オブジェクトがグローバルオブジェクトになるので、personオブジェクトのhobbyが呼び出されなくなるのですね。
これを防ぐためにES6以前の手法では、bind(this)を使っていました。

var person = {
  name: 'mejileben',
  hobby: 'programming',
  callHobbyLater: function(){
    setTimeout(function(){
      console.log('趣味は' + this.hobby);
// ここにbind(this)を書いておくとthisを確定しておける
    }.bind(this),1000);
  },
  callName: function(){
    console.log("私の名前は" + this.name);
  }
}

person.callHobbyLater();
person.callName();

bind関数の引数にオブジェクトを渡すと、呼び出し元のオブジェクトをそのオブジェクトに確定できます。
今回の場合、setTimeoutに渡す関数にとってのthisを、callHobbyLaterメソッドにとってのthis、つまりpersonオブジェクトに確定させておける効果を発動します。

実行結果
私の名前はmejileben
趣味はprogramming

ちゃんと自己紹介できましたね。
このthisを確定させるという効果ですが、先程からお話しているアロー関数式によるthisの束縛と同じ効果を持ちます。
つまり、下記のコードでも同じ動作をするということです。

アロー関数式によるthisの束縛
var person = {
  name: 'mejileben',
  hobby: 'programming',
  callHobbyLater: function(){
// アロー関数式で宣言する
    setTimeout(() => {
      console.log('趣味は' + this.hobby);
// ここにbind(this)を書いておく必要はない
    },1000);
  },
  callName: function(){
    console.log("私の名前は" + this.name);
  }
}

person.callHobbyLater();
person.callName();

ES6以前は、関数の中でthisを使う時は、本当にこいつは希望したとおりのthisなのだろうか(哲学)と考えながらコーディングし、ときにはbind(this)を付ける必要があったのですが、アロー関数になってからはその心配はなくなったということです。
基本的には、ほとんどの関数はアロー関数にして問題ないと思います。

アロー関数を使ってはいけない場面

使ってはいけない場面は簡単で、thisを束縛してはいけない場面です。
まずは通常コードから。

オブジェクトの生成とプロトタイプ
let Person = function(name,hobby){
  this.name = name;
  this.hobby = hobby;
}

Person.prototype.doJikosyokai = function(){
  console.log('私の名前は' + this.name);
  console.log('趣味は' + this.hobby)
}

let mejileben = new Person('mejileben', 'programming');
mejileben.doJikosyokai();

この結果は先程と同様、

私の名前はmejileben
趣味はprogramming

が出力されます。
2つの関数が見えますが、これらは両方ともthisを束縛していないからこそ正しく動いているコードです。

Person関数

  • newを付けて呼び出した時点でオブジェクトを生成し、thisをそのオブジェクトに割り当てた上で名前と趣味、doJikosyokai関数を格納して、変数mejilebenに渡している。

doJikosyokai関数

  • Personオブジェクトに格納されたときにthisが確定する(最初の例のprintParam関数に近い)。

両方とも、宣言の時点でthisを確定されたら困ることがわかります。
では、試しにアロー関数にしてみましょう。

アロー関数で全置換
let Person = (name,hobby) => {
  this.name = name;
  this.hobby = hobby;
}

Person.prototype.doJikosyokai = () => {
  console.log('私の名前は' + this.name);
  console.log('趣味は' + this.hobby)
}

let mejileben = new Person('mejileben', 'programming');
mejileben.doJikosyokai();

結果、めっちゃ怒られました。

Uncaught TypeError: Cannot set property 'doJikosyokai' of undefined
    at window.onload (VM102:50)

doJikosyokai関数をプロパティとしてセットできないと書いてあります。
普通の関数式で宣言していない関数に対してプロパティをセットしようとすると怒られることがわかりました。

割愛しますが、doJikosyokaiだけをアロー関数にしても変な結果になるので試してみてください。
気軽にJavaScriptの実行を試すならjsFiddle

3行でまとめ

  • JavaScriptのES6でアロー関数式という、今までとは違った関数の書き方が追加された
  • アロー関数式は既存の関数式より文字数が短くなるだけではなく、宣言時のthisを束縛して不変のものにするという効果を持っている
  • ほとんどの場面ではアロー関数式を使うほうがわかりやすくなるが、thisを束縛されて困る場面もあるので要注意!

以上です。
それでは皆さん、楽しいJavaScriptライフを!ヽ(`▽´)/

Vue.jsで作ってるサイト宣伝

せっかくなので自分で作ってるサイトを宣伝させてください!!

https://brest.nabimoon.com/

この記事を書いて一年後、Vue.js+Firebase+GCPで個人サイトを開発しました!

Webブラウザでブレインストーミングできる「ブレストLive」です。

普通に模造紙や付箋をオンラインに持ってくるだけではなく、付箋にいいねが出来たり、語句解析の人工知能が勝手にブレストに入ってきたりと、テクい工夫もしています!開発メンバーを常に募集してますので、よろしくお願いします💪

https://twitter.com/web_brestlive

参考文献

MDN
じゃあ this の抜き打ちテストやるぞー
もうはじめよう、ES6~ECMAScript6の基本構文まとめ(JavaScript)~
React tutorial- why binding this in ajax call