darakeLog

JavaScriptのアロー関数の使い方や仕様についてまとめていく

2020-04-03 04:31:24 JavaScript

本記事では、JavaScriptのアロー関数の使い方や仕様についてまとめていく。

対象読者は、JavaScriptの入門書を読み終えており、「アロー関数って何だよ」といったレベルの方を想定している。

本記事は永遠の未完成であり、随時更新していくつもり。

JavaScriptのアロー関数の使い方

JavaScriptのアロー関数は、ES2015と言うJavaScriptのバージョンで新たに追加された関数の書き方であり、モダンJavaScriptの象徴的な記法。

ES2015と呼ぶ理由は、単にJavaScriptの大きなバージョンアップがあった年が2015年というだけで、他にも「ES6」とか「ECMAScript 2015」とか「JavaScript6」と呼ばれている。

従来のJavaScriptの関数の書き方は、下記のコードのように先頭にfunctionとつける必要があり、冗長だった。

function foo(x, y){
    return x + y;
}

foo(1, 4);

しかし、アロー関数を使えば、以下のように書くことができる。

let foo = (x, y) => {
    return x + y;
}

foo(1, 4);

最初はアロー関数の方が違和感があると思うが、単純に慣れの問題だし、後述する理由により、function記法の関数よりもアロー関数の方が優れている点があるので、積極的にアロー関数を使うことになるだろう。

アロー関数の色々な書き方

アロー関数には、様々な書き方がある。一番オーソドックスなのが、先ほど紹介したコード。

let foo = (x, y) => {
    return x + y;
}

もし、関数の引数が1つの場合は、カッコを外して書くことができる。

let foo = x => {
    return x;
}

注意すべき点は、「関数の引数が1つ」という条件の場合のみ上記の書き方ができる、ということ。例えば、引数が0個や2個以上の場合は括弧が必要になる。

また、関数のなみかっこの中身が一行の場合は、以下のように書くことができる。

let foo = (x, y) => x + y;

アロー関数を一行で書くと、自動でreturnを補完してくれる。つまり、上記のx + y;の部分はreturn x + y;と同じ意味になる。

アロー関数はfunction記法と同様に無名関数も定義できる。

window.addEventListener("load", () => {
    console.log('hello world');
}, false);

アロー関数と従来のfunction関数の決定的な違いとは?

これまでの説明だと、「は?アロー関数って、ただ書き方が変わっただけじゃん」と思うかもしれないが、アロー関数とfunction関数の決定的な違いは、「thisargumentssuperと言ったキーワードの挙動が、より直感的でシンプルな動きになった」と言うことだ。

ちなみに、アロー関数の特徴は、公式サイトでは以下のように書かれている。

アロー関数式は、より短く記述できる、通常の function 式の代替構文です。また、this, arguments, super, new.target を束縛しません。アロー関数式は、メソッドでない関数に最適で、コンストラクタとして使うことはできません。 参考:アロー関数 - JavaScript | MDN

公式サイトの説明を見てみると、「this, arguments, super, new.target を束縛しません。」と書かれており、この部分がアロー関数の最大の特徴と言える。決して、書き方が特徴ではないのだ。

ただ、「束縛しません。って何だよ」と言う疑問が残るので、以下はアロー関数の特徴をfunction関数と比較しながらわかりやすく解説していく。

従来のfunction関数のthisの問題点

以前からJavaScriptのthisキーワードは、スコープによって挙動が変わるせいで、非常に扱いにくいものだった。

例えば、下記のコードを見れば分かるように、thisがどこで使われるかでthisそのものの中身が変化している。

function foo(){
    console.log(this); // windowオブジェクト
}

window.addEventListener('click', function(){
    console.log(this); // clickした要素のオブジェクト
}, false);

上記のようなシンプルなコードだとまだ良い。特に問題なのが、JavaScriptでオブジェクト指向をしたい場合に弊害が起こることだ。

class Foo {
    constructor(elm, y){
        this.elm = elm; // domが入る
        this.y = y;
    }

    addCount(){
        this.elm.addEventListener('click', function(){
            this.y++;
            console.log(this.y);
        }, false);
    }
}

上記のaddCountメソッドを見て欲しい。addCountメソッドでは、this.elmがクリックされると、this.yに1が追加されてコンソールに表示されるようにしている。

ここでのthisは、Fooクラスのインスタンスを指し示すことが期待されているが、実際はクリックをした要素であるthis.elmthisとして使われることになってしまう!

上記の原因は、JavaScriptのthisは、現在のスコープによって自身の挙動を変える、と言う性質を持っているからだ。

通常クラスのメソッド内のthisはそのクラスのインスタンス(上記の例だと、this.elmとかthis.yとか)を指し示す。しかし、今回はそのメソッドの内側にaddEventListener('click'を書いているため、メソッドのスコープの内側にaddEventListenerの関数のスコープが作られて、そのaddEventListenerの関数のスコープをチェックして、thisの挙動が決定されるからだ。

今までもJavaScriptのthisの挙動は問題視されてきたが、フロントエンドの進化が進むにつれて、「JavaScriptでもオブジェクト指向でガンガン作ろうぜ!」と言う風潮になってきて、thisの挙動が致命的な欠点となってきたのだ。

thisの欠点を克服したアロー関数

上記の「thisが変な動きをする問題」を解決するために生まれたのが、アロー関数となる。冒頭で公式サイトが、

「アロー関数は、this, arguments, super, new.target を束縛しません。」

と書かれているのを見たが、これは「this, arguments等の挙動はアロー関数内のスコープをチェックして決めるのではなく、アロー関数の1つ外側のスコープを見て、thisやargumentsの挙動を決定する」と言う意味の説明なのだ。

例えば、先ほどのclassのaddEventListenerの部分をアロー関数に書き換えたとする。

class Foo {
    constructor(elm, y){
        this.elm = elm; // domが入る
        this.y = y;
    }

    addCount(){
        this.elm.addEventListener('click', () => {
            this.y++;
            console.log(this.y);
        }, false);
    }
}

先ほどはaddEventListener内のthis.y++のthisの部分がクラスのインスタンスではなく、クリックした要素を指し示す、と言う期待に反する挙動をしていた。

しかし、addEventListenerの第2引数をfunction関数ではなく、アロー関数にすることで、this.y++;のthisはアロー関数内のスコープではなく、1つ外側のaddCount()メソッドのスコープをチェックして挙動を決定するようになる。

その結果、this.y++のthisの部分はFooクラスのインスタンスを指し示すようになり、期待通りの処理が行えるようになる。