TypeScriptの”=>”はFunction Type以外にArrow Function Expressionがある

前回の記事「TypeScriptの”=>”って何?」でまるで「Function  Type(関数型)でしか使用しない」という記事を書いてしまったのですが、さっそく加筆修正してこちらでもう一つの用途について書こうと思います。早い段階で気付いてよかった。

型宣言以外では何に使うのか?という話ですが、その前にこのコードを見てください。このコードはVisual Studio ExpressでTypeScriptのテンプレートプロジェクトに入っているTypeScriptのコードです。(コードを表示しているところにCoffeeScriptとなっているのは気にしないでください。)

class Greeter {
    element: HTMLElement;
    span: HTMLElement;
    timerToken: number;

    constructor(element: HTMLElement) {
        this.element = element;
        this.element.innerHTML += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }

    start() {
        this.timerToken = setInterval(() => this.span.innerHTML = new Date().toUTCString(), 500);
    }

    stop() {
        clearTimeout(this.timerToken);
    }

}

window.onload = () => {
    var el = document.getElementById('content');
    var greeter = new Greeter(el);
    greeter.start();
};

このコード、”=>”の右側に戻り値の型が入っている式は1つも存在しません。どこにいった関数型!?
で、これなんですが、更に調べてみるとArrow Function Expressions(直訳して「矢関数式」「矢印関数式」「アロー関数式」?)とかArrow Function Syntaxというもの。ECMAScript 6で提案されている実装です。TypeScriptはECMAScript 6をベースにしているのでこういうのも(いい意味で)入ってきます。

で、何がどうなるのか?ですが、だいたい以下の特徴があります。

  • 関数式をより短く記述できる
  • thisをthisとして扱える

2つ目は特に重要ですが、まずは順に説明します。

関数式をより短く記述できる

これがわかればアロー関数式の半分以上は理解できたようなものです。

関数式をアロー関数式に置き換えることが可能です。例えば、こう。

// 1) 関数式。匿名関数をaddFuncBasicExprに代入
var addFuncBasicExpr = function (a, b) { return a + b; }
// 2) アロー関数式。1)と同じ内容。
var addFuncArrowExpr = (a, b) => { return a + b; };
// 3) 2)はこうのようにも記載可能
var addFuncArrowExpr1 = (a, b) => a + b;

もともと関数式は1)のように書いていましたが、それを2)のように記載することが可能です。()=>{}が1つの塊です。見た目通り「function」がなくなり「=>」が加わったぐらいの認識でよいかと思います。関数本体は1行だとか1つの命令だとかの縛りはなく、2行だろうが10行だろうが特に問題ではありません。

アロー関数式では色々と省略が可能な文法になっており2)を3)のように記載することが可能です。実行するのが一行で右辺だけの式であれば、その実行結果を戻す関数になります。
その他、引数を取らないのであれば()だって省略可能。可読性の観点からあまりお勧めはしませんが…。

上記の例をJavaScriptにコンパイルすると次のようになります。

// 1) 関数式。匿名関数をaddFuncBasicExprに代入
var addFuncBasicExpr = function (a, b) {
    return a + b;
};

// 2) アロー関数式。1)と同じ内容。
var addFuncArrowExpr = function (a, b) {
    return a + b;
};

// 3) 2)はこうのようにも記載可能
var addFuncArrowExpr1 = function (a, b) {
    return a + b;
};

当然、 どれも同じですね。

今回の例では簡略な書き方をしましたが、もちろん型を宣言することも可能です。

var calcFunc: (a: number, b: number) => number;

// 1) 関数式。匿名関数をaddFuncBasicExprに代入
calcFunc = function (a: number, b: number): number { return a + b; }
// 2) アロー関数式。1)と同じ内容。
calcFunc = (a: number, b: number): number => { return a + b; };
// 3) 2)はこうのようにも記載可能
calcFunc = (a: number, b: number): number => a + b;

上記3つはすべて構文エラーにはなりません。calcFuncの戻り値の型をnumberからstringに返ると途端に構文エラーになります。

thisをthisとして扱える

今までJavaScriptを触ったことをある人であれば「このthisはどこのthis…?」と悩んだり、thisで参照したのにglobalが取れたりundefinedが返ってきたりと悩まされた方はけっこういるかと思います。アロー関数式で定義した処理はthisがthisであることを許してくれます。

アロー関数式はECMAScript 6のプロポーザル段階です。現時点(2015/01/03)でJavaScriptは、当たり前ですが、ECMAScript 6をサポートしていないのでアロー関数式はサポートしてません。TypeScriptではJavaScriptにコンパイルすると以下のようなJavaScriptを生成します。

まずはTypeScript。この記事の最初に載せたサンプルに少し手を加えました。

class Greeter {
    element: HTMLElement;
    span: HTMLElement;
    timerToken: number;

    constructor(element: HTMLElement) {
        this.element = element;
        this.element.innerHTML += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }

    startArrow() {
        this.timerToken = setInterval(() => this.span.innerHTML = new Date().toUTCString(), 500);
    }

    startBasic() {
        this.timerToken = setInterval(function () { this.span.innerHTML = new Date().toUTCString() }, 500);
    }

    stop() {
        clearTimeout(this.timerToken);
    }
}

加えた内容は、startをstartArrowに変えました。比較としてstartBasicを作成しました。
startArrowはsetIntervalの引数にアロー関数式で定義した関数を指定しています。対してstartBasicは通常の関数式で定義しました。

そういえば、このコードが何をしているかの説明をしてませんでした。このコードは「Greeterクラスのインスタンス作成時に指定したHTMLのエレメントに対して現在時刻をリアルタイムに更新し続ける」というものです。そのためsetIntervalを用いて500ms間隔で現在の時刻をHTMLのエレメントに更新しています。

上記をJavaScriptにすると以下のようになります。

var Greeter = (function () {
    function Greeter(element) {
        this.element = element;
        this.element.innerHTML += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }
    Greeter.prototype.startArrow = function () {
        var _this = this;
        this.timerToken = setInterval(function () {
            return _this.span.innerHTML = new Date().toUTCString();
        }, 500);
    };

    Greeter.prototype.startBasic = function () {
        this.timerToken = setInterval(function () {
            this.span.innerHTML = new Date().toUTCString();
        }, 500);
    };

    Greeter.prototype.stop = function () {
        clearTimeout(this.timerToken);
    };
    return Greeter;
})();

違い、ありますよね。startArrowにはstartArrow呼び出し時にthisを_thisに代入し、setIntervalで指定した関数内で_thisを用いることでthisによる問題を解決しています。
この方法自体はよくみる解決方法の1つですね。

対してstartBasicでは単にthisを呼んでいるのでよくある問題を解決できていません。

現在のJavaScript(厳密にはそのエンジン)はECMAScript 6に対応していないのでTypeScriptからJavaScriptを生成するとこのように生成します。各ブラウザが対応できれば不要なコードになるのでしょうが、全てのユーザが突然ECMAScript 6なJavaScriptをサポートしたブラウザになるわけではないので数年はこのままですね。

まとめに

関数型から始まり、アロー関数式やthisのスコープ問題に関するところまで来たわけですが、”=>”は関数型の宣言や簡単な関数式にも使えます、ってことがわかりました。

今回参考にしたサイト。

  • lambdas and using ‘this’ – Handbook(TypeScript[公式])
    ここではlambda syntaxとして説明していますね。説明している内容はJavaScriptにおけるthisの扱い難さと、その問題については「JavaScriptのfunctionよりも()=>{}を使うことで解決出るよ」と説明してます。
  • TypeScript
    ページトップの「learn」から「language spec」をクリックすると言語仕様書のPDFが開きます。「4.9 Function Expressions」と「4.9.2 Arrow Function Exprssions」を読めばだいたいわかります。
  • harmony:arrow_function_syntax – wiki.ecmascript.org
    アロー関数式に関するECMAScript 6仕様(草案)の内容。

では。