2018年に見直した
現代的なJavaScriptの記法を紹介するぜ

449
418
497

2015年6月にECMAScript 2015がリリースされて以降、JavaScriptの機能は大きく強化されました。const/let、アロー関数、クラス構文、Promiseなどが有名なところですが、ES2016、ES2017、ES2018、そしてさらにその先へJavaScriptの仕様は日々進化しています。筆者はここ数年JavaScript(主にTypeScript)で開発していますが、これまで当たり前だと思っていた手法を新しい新機能で置き換えることが何度もありました。

本記事では、2017から2018年に向けて筆者が見直したJavaScriptのプログラミング手法について紹介します。記事の末尾にブラウザ対応の手法もまとめていますので、あわせてご覧ください。

配列要素の存在チェックにはincludes()メソッド

配列内に特定の要素があるかどうかを調べる場合、従来は次のようにindexOf()メソッドを使っていました。

// 対象の配列
var targetArray = ["鈴木", "田中", "高橋"];

// 田中が存在するかどうか
if (targetArray.indexOf("田中") >= 0) {
  console.log("田中が含まれています");
} else {
  console.log("田中は存在しません");
}

ES2017で追加された配列のためのincludes()メソッドを使うと、インデックスではなく真偽値を使って配列内の要素の存在チェックが可能になります。

// 対象の配列
const targetArray = ["鈴木", "田中", "高橋"];

// 田中が存在するかどうか
if (targetArray.includes("田中")) {
  console.log("田中が含まれています");
} else {
  console.log("田中は存在しません");
}

ちなみにES2015では文字列の存在チェックのためのincludes()メソッドが追加されており、こちらもindexOf()と代わって使用しています。

// 対象の文字列
const targetUserAgent = navigator.userAgent;

// ES 5以下
// if (targetUserAgent.indexOf("iOS") >= 0) {
//   console.log("iOSブラウザです");
// }

// ES2015以上
if (targetUserAgent.includes("iOS")) {
  console.log("iOSブラウザです");
}

筆者のQiita記事「配列内の要素の存在確認にはArray.includes()メソッドが便利」もあわせて参照ください。

ゼロパディングにはpadStart()メソッド

時計の秒数表示など、一桁の数字の頭に0をつけて文字列にしたい場合、従来は次のようなコードを書いていました。

// 現在時刻の秒数を取得
var second = new Date().getSeconds();

// 0パディングされた文字列
var paddedSecond = String(second);

// 10秒未満なら、冒頭に0を付与する
if (second < 10) {
  paddedSecond = "0" + paddedSecond;
}

// 現時刻の秒数 01, 09, 12, ...
console.log(paddedSecond);

ES2017では、文字詰めのためのpadStart()メソッドが追加されました。次のように指定することで、「文字列」が指定の「長さ」になるように、「詰める文字」を文字列の前に配置します。

文字列.padStart(長さ, 詰める文字);

先ほどのコードは次のように短く書けます。

// 現在時刻の秒数を取得
const second = new Date().getSeconds();

// 2桁の文字列になるよう文字詰め
const paddedSecond = String(second).padStart(2, "0");

// 現時刻の秒数 01, 09, 12, ...
console.log(paddedSecond);

Promiseを使うならasync/awaitをセットに

ES2015で非同期処理のために導入されたPromiseですが、Promiseの処理を連結し、条件分岐を入れるなどすると見通しが悪くなりがちでした。

次に示すのは、リソース取得のためのFetch APIを用いてJSONデータの中身を出力する例です。エラー時にはエラー内容を出力します。

fetch("./myjson.json")
  .then(
    response =>
      new Promise((resolve, reject) => {
        return response
          .json()
          .then(
            json => resolve(json),
            error => reject(error)
          );
      })
  )
  .then(
    json => console.log(Reflect.get(json, "mytext")),
    error => console.log(`error : ${error}`)
  );

※ コード中のReflect.get()を使うとオブジェクトから指定のプロパティを取り出せます。

ES2017で導入されたawait/asyncを使えば、次のようにネストを深くせずにPromiseによる処理を記述可能です。

async function main() {
  try {
    const json = await (await fetch(
      "./myjson.json"
    )).json();
    console.log(Reflect.get(json, "mytext"));
  } catch (error) {
    console.log(`error : ${error}`);
  }
}

main();
  • await演算子: Promiseによる処理の結果が返るのを待つ
  • async: 関数内でawaitを使うための宣言

整数部分の取得にはMath.trunc()メソッド

小数を整数に丸める場合、Math.floor()を使うことが多かったのですが、負の値を丸める場合の挙動が分かりづらく、複数人開発での混乱の元になりがちでした。

Math.floor(6.5); // 6
Math.floor(-6.5); // -7 (-6ではない)

ES2015で導入された整数部分だけを取得するMath.trunc()メソッドを使えば、正の値、負の値が関係なく整数部分を取得でき、直感的な挙動となります。

Math.trunc(6.5); // 6
Math.trunc(-6.5); // -6

べき乗の計算にはべき乗演算子**

2の5乗などのべき乗計算には、従来はMath.pow()を使っていました。

Math.pow(2, 5); // 32
Math.sqrt(Math.pow(3, 2) + Math.pow(4, 2));
// (3の2乗 + 4の2乗)のルート。5

ES2016ではべき乗演算子**(Exponentiation Operator)を使って同じようにべき乗計算が可能になりました。タイプ量が少なくなるメリットがあります。

2 ** 5; // 32
(3 ** 2 + 4 ** 2) ** 0.5;
// (3の2乗 + 4の2乗)のルート。5

筆者のQiita記事「べき乗をするならMath.pow()よりもExponentiation Operatorが手軽」もあわせて参照ください。

文字数カウントには配列用のスプレッド演算子(...

文字数をJavaScriptでカウントする場合は、Stringlengthプロパティを用いてきました。しかし、このプロパティではサロゲートペア文字列(「𦥑(臼ではありません)」や、「𩸽(ホッケ)」などの漢字、「😀」「😺」といった絵文字)の文字数が2以上になります。

"𦥑".length; // 2
"今日は☀️です".length; // 7

ES2015で導入された...」で表現されるスプレッド演算子。文字列と配列を組み合わせると、文字数のカウントに使えます。

[..."𦥑"].length; // 1
[..."今日は☀️です"].length; // 6

なお、JavaScriptにおける文字数の数え方についてより詳しくは記事「JavaScript における文字コードと「文字数」の数え方 | blog.jxck.io」を参照ください。

オブジェクトのコピーにもスプレッド演算子(...

ES2015では、オブジェクトのコピーのためのObject.assign()が導入され、オブジェクトのコピー(シャローコピー)が楽になりました。

const myObject = {
  result: true,
  members: [
    { id: 1, name: "鈴木" },
    { id: 2, name: "田中" },
    { id: 3, name: "高橋" }
  ]
};

// オブジェクトのコピー
const copiedObject = Object.assign({}, myObject);

console.log(copiedObject);
// オブジェクトがコピーされる
// {
//    result: true,
//    members: [
//      { id: 1, name: "鈴木" },
//      { id: 2, name: "田中" },
//      { id: 3, name: "高橋" }
//    ]
//  }

ES2018で導入予定のオブジェクト用のスプレッド演算子を用いれば、次のような短いコードでオブジェクトのコピーが可能です。

const myObject = {
  result: true,
  members: [
    { id: 1, name: "鈴木" },
    { id: 2, name: "田中" },
    { id: 3, name: "高橋" }
  ]
};

// オブジェクトのコピー
const copiedObject = { ...myObject };

console.log(copiedObject);
// オブジェクトがコピーされる

非同期処理はObservable(RxJS)

筆者は2017年頃からSPA(Single Page Application)案件の開発が増えてきました。SPAではページ遷移なしにAjaxでデータを取得してコンテンツが展開します。それらは非同期処理で行われるわけですが、基幹技術としてObservable(RxJS)を使用するようになりました。ObservableはDOMイベント(クリック、スクロール)やタイマーイベント、通信といったプッシュ型のデータ・ソースの取扱いが可能です。

// 1秒ごとに1, 2, 3, 4,...のデータを流し、
// 流れてきた値を出力する
Observable.interval(1000).subscribe(value =>
  console.log(value)
);

Observableの利点は、データソースの加工が容易なこと。たとえば、「データに応じて処理を分岐させる」「遅延してデータを流す」など、多くの操作が可能です。

参考までにTypeScriptとAngularでAPI通信処理を行うコードを紹介します。callApiObservableがAPI通信を行うObservablefilter()は条件にあったデータだけを流すOperatorsubscribe()メソッドは加工済みのデータを受け取るためのメソッドです。

import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable";

/**
 * とあるコンポーネント
 */
export class FooComponent {
  constructor(private http: HttpClient) {
    this.fetchData();
  }

  /**
   * APIの通信処理
   */
  fetchData() {

    // api/bar_api.phpをコールし、
    // レスポンスとしてerror_messageキーを持ちうるオブジェクトを受け取る
    const callApiObservable = this.http.get<{ error_message?: string }>("api/bar_api.php");

    // 通信成功時用購読処理
    callApiObservable
      .filter(response => Reflect.has(response, "error_message"))
      .subscribe(
        response => console.log(`error:${Reflect.get(response, "error_message")}`)
      );

    // 通信失敗時用購読処理
    callApiObservable
      .filter(response => Reflect.has(response, "error_message") === false)
      .subscribe(
        response => console.log(`success:${Reflect.get(response, "error_message")}`)
      );

  }
}

※ Angular 5.2, TypeScript 2.7, RxJS 5.5のコードです

ObservableはECMAScriptで現在仕様策定の段階にあります(参考「Observables for ECMAScript」)。まだ策定されていないとはいえ、Observable(加えてOperatorsSchedulers)の考え方は、Reactive Extensions(Rx)の一環として数年前から盛り上がっていて、他の言語ではRxSwiftRxKotlinRxJavaといったライブラリが対応しています。Ajax、非同期処理を多用するウェブコンテンツとも相性がよいため、今後はJavaScriptでも広く使われる技術と筆者は予想しています。

未対応ブラウザで最新APIを使うには

本記事で紹介したのはすべてES2015以上の仕様のもので、ES2018の機能とObservableを除けばChrome、Firefox、Edge、Safariの各最新版で使えます。未対応ブラウザでもこれらの機能を使うには、PromisepadStart()などのAPIであればポリフィルを、スプレッド演算子などのシンタックスであればTypeScriptやBabelでのトランスパイルにより対応します。IE 11やAndroid 4系など案件の対応が求められる旧式ブラウザでも、最新機能が動作するようになります。

ポリフィルはいくつかありますが、筆者はよくnpmモジュールのcore-jsを使っています。今回の例ではObservable以外に対応します。npmを使ってcore-jsをインストール後、次のようにimportで読み込んで使用します。

import "core-js/es7/string";

// 文字列123の冒頭に「0」を付与する
"123".padStart(2, "0");

Observableについては、実装している現行ブラウザは存在しないので、npmモジュール「rxjs」をインストール後、importで読み込みます。

import { Observable } from "rxjs/Observable";
import "rxjs/Rx";

Observable.interval(1000).subscribe(value =>
  console.log(value)
);

最新シンタックスの対応についてですが、筆者は複数人による中規模プロジェクト開発が多いので、静的型付けが行えるTypeScriptをよく使います。ポリフィルのバンドルと合わせて、モジュールバンドラーにはwebpackを採用しています。

次の記事でwebpackによるモジュールのバンドル方法や、TypeScriptやBabelの環境構築方法について紹介しているので、あわせて参照ください。

また、本記事で紹介した機能について、実装されたECMAScriptバージョンと対応方法についてまとめました。モジュールバンドラーやトランスパイラーの設定時の参考にしていただければと思います。

| 機能 | ESバージョン | 未対応ブラウザ向け

対応方法
includes() (文字列)
Math.trunc()
配列のスプレッド演算子(...)(※)
Promise
includes()(配列)
べき乗演算子(**
padStart()
await / async
+トランスパイル
オブジェクトのスプレッド演算子(...
Observable

※ TypeScriptは配列のスプレッド演算子には対応していますが、本記事で紹介した[...文字列]という記法には未対応です。

2019年に向けてのJavaScript

ES2015以降、JavaScriptのは毎年メジャーバージョンアップするようになり、新しい機能が次々に追加されています。そのアップデートに追従することで、プログラミングが効率化する、堅牢なプロジェクト作成ができるといったメリットに繋がります

ECMAScriptの仕様を策定している委員会TC39のブランチでは、ES2018以降に導入されるJavaScriptの仕様が議論されているので、興味のある方は見てみるとよいでしょう。

個人的に気になっているのは、次の機能です。

本記事では、筆者の視点から見直したJavaScriptの機能をまとめました。自分は新機能をこう使っている、この機能が便利といった意見がありましたら、是非Twitterはてなブックマークのコメントでお寄せくださいませ。