猫の手ツール

Step 1: Web Basics

03. アロー関数の書き方:this・return・通常の関数との違い

アロー関数は、短いコールバックや変換処理を表す方法です。通常の関数とthisの扱いが違うため、単にfunctionを短くした記法として置き換えないようにします。猫の手ツールの保存ガイドを開く処理と、小さな独立例で使い分けを確認します。

function って書かないの?

関数といえば function() だと覚えていたのですが、ES6(2015年)以降の最近のJavaScriptでは、 () => という書き方(アロー関数)が頻繁に使われます。

短いコールバックを読み始めるときは、引数・本体・返り値の3つを先に探します。

// 昔の書き方
btn.onclick = function(event) {
    console.log("クリックされました");
};

// 今の書き方(アロー関数)
btn.onclick = (event) => {
    console.log("クリックされました");
};

さらに、引数が1つだけの場合は ( ) を省略できたり、本体が1つの式である場合は や return すら省略できるという柔軟性も持っています。

短く書けるだけじゃない。最大の理由は「this」

アロー関数はfunctionの単なる省略形ではなく、thisを作らず外側のthisを参照する関数です。

従来の function で起きる「thisの迷子問題」

通常の関数のthisは呼び出し方で変わります。setTimeoutへ渡すコールバックは、オブジェクトのメソッドとして呼び出される場合と区別します。

const obj = {
    name: "猫の手ツール",
    greet: function() {
        // obj.greet() と呼び出した場合、この this は obj を指す
        console.log("I am " + this.name); 

        setTimeout(function() {
            // ブラウザでは this は通常 window。obj.name にはならない
            console.log("Hello from " + this.name); 
        }, 1000);
    }
};

昔のJavaScriptエンジニアは、この問題を回避するために const self = this; といった変数をわざわざ作って対処していました。

アロー関数で解決!

アロー関数は「自分が定義された場所の this をそのまま引き継ぐ」という特性を持っています(レキシカルスコープと呼ばれます)。これにより、直感的にコードが書けるようになります。

const obj = {
    name: "猫の手ツール",
    greet: function() {
        setTimeout(() => {
            // アロー関数なら、外側の this.name ("猫の手ツール") をそのまま使える!
            console.log("Hello from " + this.name); 
        }, 1000);
    }
};

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

とても便利なアロー関数ですが、実は何でもかんでもアロー関数にすれば良いわけではありません。以下のような場面では、従来の function を使う必要があります。

  • オブジェクトのメソッド定義: 上記の例の greet のように、オブジェクト自身のプロパティにアクセスしたい場合は function を使います。
  • イベントリスナーで this から登録先の要素を取得したい時: addEventListener 内で this を使ってクリックされた要素(e.currentTarget相当)を取得したい場合は、アロー関数だと this がズレてしまいます。

呼び出し方と返り値から選ぶ

猫の手ツールの実際のコードでも、配列の操作(map や filter)や、UIのイベント処理にアロー関数を使えます。イベントの登録先は、アロー関数でも event.currentTarget で取得できます。

短い変換処理にはアロー関数、呼び出し元に応じた this が必要な処理には通常の関数、というように役割から選べます。アロー関数は new で呼び出すコンストラクターには使えません。

省略記法で返す値を確認する

式をそのまま返す形と、波括弧で処理を書く形では return の要否が違います。以下はそれぞれ独立した関数です。オブジェクトを直接返す場合は丸括弧で囲みます。

const double = value => value * 2;
const doubleWithBlock = value => { return value * 2; };
const makeSize = width => ({ width, height: width });
console.log(double(3), doubleWithBlock(3), makeSize(200));
// 6 6 { width: 200, height: 200 }

保存ガイドの操作は、閉じた変数を使うコールバック

画像変換ツールでは、取得済みのiphoneSaveModalをアロー関数の中で参照します。呼び出し元のthisを使わず、true/falseを明示的に渡す設計です。

const toggleModal = (show) => {
  if (iphoneSaveModal) {
    if (show) iphoneSaveModal.classList.remove('hidden');
    else iphoneSaveModal.classList.add('hidden');
  }
};
if (btnModalOpen) btnModalOpen.onclick = () => toggleModal(true);
if (btnModalClose) btnModalClose.onclick = () => toggleModal(false);

これは本番コードから型指定を省いた抜粋です。ボタン自身が必要ならevent.currentTargetを読む方法もあり、「イベントだから通常の関数」と一律に決める必要はありません。制作解説の保存までの流れと、イベントの登録方法を合わせて確認してください。

まとめ

波括弧を付けたのに return を書かなかった場合、返り値は undefined になります。map の結果が空のように見えるときは、まずこの違いを確認します。

参考資料