jQueryとアロー関数の微妙な関係


昨日のJSLounge #0 jQueryだけで頑張らないWeb開発ハンズオン@potato4dに教えてもらったアロー関数の話。

jQueryからvue.jsへ

ハンズオンのテーマは、jQueryを使って書かれたサイトをvue.jsに置き換えようというものでした。

$("button[type='submit']").html("<i class='glyphicon glyphicon-repeat'></i>");
$.ajax({
  url: "https://example.com/events"
}).done(function(data){
  $("button[type='submit']").html("送信する");
})

ボタンをローディング表示にしてAPIを呼び出し、終了後にボタンを元に戻す、というコードです。

ハンズオンでできる範囲ということで、API呼び出しそのものは$.ajaxのままでやってみましょう(つまり、ボタンの書換部分をvue.jsのv-ifで書くところをやってみよう)という感じでした。

<button v-if="!loading">送信する</button>
<button v-else><i class='glyphicon glyphicon-repeat'></i></button>

v-if='loading'をつけたボタンと v-elseをつけたボタンを用意すると、JSコードでvue.loadingを変更するのに応じてボタンの表示が切り替わります。

this.loading = true;
$.ajax({
  url: "https://example.com/events"
}).done(function(data){
  this.loading = false;
})

このコードで実際動かしてみると、ボタンを押してローディングにはなるのですけど、その後ボタンがもとに戻らなくなってしまいました。

thisとアロー関数

}).done(function(data){
  this.loading = false;
})

問題はこの部分です。

jQuery単体でもたまに問題になるのですが、ajax関数のコールバック内のthisは、外部でのthisと異なるものを指します。$(this) って書いたら動かなくなるアレです。
このため、this.loadingはvue.jsオブジェクトではない別のオブジェクトのloadingプロパティ(たぶん誰も参照していない)を書き換えるだけの空回り状態になってしまって、ボタンがもとに戻らなくなってしまっています。

@potato4dは「あとで説明するので、いったんこう書き換えてください」という豪快な説明で こう書き換えました。

}).done((data) => {
  this.loading = false;
})

説明してもらうのを待てずにググったところによると、JavaScript ES2015で導入されたアロー関数というものです。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/arrow_functions

従来のfunction(data){で書くと、その中のthisは文脈によって変化します。
一方アロー関数を使った場合、関数の中であっても、thisは関数の外と同じものを指します。つまりこの場合だと引き続きvue.jsオブジェクトにアクセスすることができるので、this.loading = false;はvue.jsのコードとして動作します。

アロー関数とIEの微妙な関係

アロー関数は新しいJavaScriptの書き方なので、環境によって動かないことがあります。具体的には、IE11、iOS9、Android4あたりでは動きません。
http://caniuse.com/#feat=arrow-functions

vue.jsを勉強しに来た参加者がIE11なんて使わないことを見越してアロー関数書いた @potato4d はハンズオン上手だと思うのですけど、実際運用する上でAndroid4をバッサリ切るのは無理なので、その辺のところを質疑応答のときに聞いてみました。

まず、IE11を含む古いブラウザでも動かす方法として、Babelをつかって新しい書き方を古いOSでも動くコードにコンパイルしてしまう方法があります。ある程度の規模になるとこのやり方が必須だし、個人的にはこっちを使いたいのですが、「npmを入れてwebpackからgulpをつかってBabelを呼び出してね」というと、環境設定だけでハンズオンが終わってしまうし、jQueryから乗り換えようという人には厳しい予感がします。

で、お手軽な解決法としては、従来のjQueryでも使われていたselfを使うテクニックが使えます。

var self = this;
self.loading = true;
$.ajax({
  url: "https://example.com/events"
}).done(function(data){
  self.loading = false;
})

selfにthisを退避した上で使うことで、doneの中でも外と同じオブジェクトにアクセスすることができます。

vue.jsを使うに当たってはwebpackを始めとした近代JavaScript環境の勉強が必須だと思っていたのですけど、なるほどコンパクトにまとめる方法もあるものだなあと思いました。