Babelを使うとアロー関数内のthisがundefinedになる
ややこしい問題に直面したので、メモしておく。
問題
次のように、jQueryでコールバック関数にアロー関数を用いたスクリプトを記述する。
$("item > title").each(() => { let title = $(this).text(); console.log(title); });
このES6の構文ををBabelでES5へトランスパイルすると...
$("item > title").each(function(idx) { var title = $(undefined).text(); console.log(title); });
アロー関数の内のthis
がundefined
になってしまう。
問題は、どうやらトランスパイル前後の、each
関数でのthis
の扱いの違いによるもののようだ。
以下、シンプルな例を考えてみる。
var arr = [1]; $(arr).each(() => { console.log(this); /* Window {} */ }); $(arr).each(function() { console.log(this); /* Number {} */ }); $(arr).each((idx, value) => { console.log(value); /* 1 */ });
アロー関数のthis
はwindowオブジェクトを指し、無名関数では要素を指す。
これをBabelに通すと、自動的に"use strict"
モードになり、スクリプトを実行した結果は次のように変わる。
/* "use strict" */ var arr = [1]; $(arr).each(() => { console.log(this); /* undefined */ }) $(arr).each(function() { console.log(this); /* 1 */ }) $(arr).each((idx, value) => { console.log(value); /* 1 */ });
"use strict"
モードが適用され、非"use strict"
モードのアロー関数ではwindowオブジェクトにbindされていたthis
が、undefined
になっている。
each
関数内でのthis
は要素を指すが、アロー関数内のthis
はそのコンテキストのthis
に等しい。つまり、this === windowオブジェクトが成り立つ。加えて、"use strict"
モードにおいては、このようなthis
はundefined
になるため、今回のような問題が発生する。
まとめ
まとめると、jQueryのコールバック関数で注意すべき点としては:
- 無名関数内では、
this
は要素を指す。 - アロー関数では、
this
はそのコンテキストのthis
に等しい。
これに加えて、Babelを用いると、"use strict"
モードが適用され、今回のように、windowオブジェクトを指していたアロー関数内のthis
はundefined
になる。
解決策
この結果を踏まえると、今回の解決策としては、
- そもそもアロー関数をやめる
- 要素の値である引数を使う
/* アロー関数をやめる */ $("item > title").each(function() { let title = $(this).text(); console.log(title); }); /* 要素の値である引数を使う */ /* 第一引数がインデックス、第二引数が要素の値 */ $("item > title").each((idx, value) => { let title = $(value).text(); console.log(title); });
多分あっているとは思うが、理解がふわふわしている。