ES6(ECMAScript6)でのJavaScriptの書き方

ECMAScriptとは

ECMAScript(エクマスクリプト)とは、JavaScriptの言語仕様です。

これは、たとえばHTML5で言うところのW3C勧告のような位置づけで考えてもらうと分かりやすいと思います。

InternetExplorerやFirefox、GoogleChromeなどの各ブラウザは、JavaScriptランタイム(実行環境)が標準で搭載されていますが、各ブラウザ開発者はこのECMAScriptの仕様を元にしてJavaScriptランタイムへ実装しています。

現在、ES6(ECMAScript6)が2015年6月に策定され、今後ES6の仕様を基本にしてJavaScriptランタイムが実装されていくことになります。(参考:At the June 17, 2015 Ecma General Assembly in Montreux, ECMA-262 6th edition - ECMAScript® 2015 Language Specification and ECMA-402 2nd edition - ECMAScript® 2015 Internationalization API have been adopted

ES6(ECMAScript6)でのJavaScriptの書き方

草案を抜けて策定に至ったことですし、どうせ将来的にES6で書き換える可能性があるのであれば今からES6で書いておいても良いと思います。

とはいえ、まだブラウザ側のJavaScriptランタイムの機能実装がES6の仕様に追いついていないため、ES6で書いたJavaScriptをそのまま実行することが困難です。

そのため、Babelなどを利用してES6で書いたコードをES5にトランスパイルして使用します。

Babelは基本的にNode.js上で動作するため、既にnpmが使える人であればすぐにでもES6の記述に切替可能です。

また、BabelはGruntやgulp、webpackなどを通して実行することができるため、既にこれらのタスクランナーやモジュールバンドラーなどのツールを使用している人であれば、すぐにでもES6の記述に切替可能です。

Babel以外にも同様のものは存在するのですが、Reactのために書かれたJSXにも使えますので、WILDWEST-SERVICEではBabelを使用しています。

ES6になって何が変わったのか

ES6では、いくつか新しい記述方法が加わりました。

以下に紹介している内容は、執筆時点ではGoogleChromeでもまだ動作しない内容を含んでいます。

基本的にはFirefoxのほうが先行して実装されているイメージでしたので、JSFIDDLEやPlunkerで試してみようと思っている方はFirefoxで開いたほうが良いかもしれません。

ES6に関する各ブラウザの対応状況は、ECMAScript 6 compatibility tableを参照してください。

参考書籍のご紹介

今回の記事を執筆する際に色々と資料を探していたところ、WEB+DB PRESS Vol.87でECMAScript6が特集されており、たいへんよくまとまっていました。(著者はサイボウズの佐藤鉄平さん@teppeis

FluxとReactによるフロントエンド開発の特集もされていましたので、ぜひ手にとっていただきたい1冊です。

let

前回の記事、```jsに:けるletとvarの違い](https://www.wildwest-service.com/let_and_var/)で紹介した、局所変数の宣言で

ifやfunctionなどのブロックスコープ内でvarの代わりに宣言として記述することで、ブロックスコープ内でのみ有効な変数として使うことができるようになります。

Arror Function

無名関数を記述する場合の新しい書き方です。

次のように記述することができます。

var sum = (x, y) => return x + y;

これをES5までの書き方で記述すると次のようになります。

var sum = function (x, y) {
  return x + y;
}

importとexport

まず、export.jsというファイルがあったとして、次のような内容が記述されているとします。

export function foo() {
  console.log('foo');
}

export function bar() {
  console.log('bar');
}

そして、import.jsというファイルに次のような内容が記述されているとします。

import {foo, bar} from './export.js';

foo();

bar();

上記のソースでは、export.jsで「export」とした関数foo()とbar()がimport.jsにimportされます。

importとexportを使用すると、foo()とbar()は、実行ファイル側(import.js)で別に関数を記述しなくても、export.jsから関数を取り出して使用することができるようになります。

これにより、ソースコードの再利用性と拡張性が高まり、リユーザブルでスケーラブルなJavaScriptを記述できます。

Rest Parameter

関数の引数を可変の配列として受け取ることができます。

function rest(...array) {
  console.log(array);
}
rest(10, 20, 30);

上記の例ではrest()に3つの引数を渡していますが、Rest Parameterの記述(...array の部分)をすることにより、「array」という1つの配列という引数として扱われます。

Rest Parameterとして記述された引数以降はすべて1つの配列となってしまうため、Rest Parameterとする部分は必ず引数の最後に記述しなくてはいけない点に注意が必要です。

Default Parameter

関数の引数に対して初期値を設定することができます。

初期値が設定されていれば、関数を実行する際に引数を省略されても、undefinedでエラーになるのを防ぐことができます。

function add(arg1=5, arg2=10) {
  console.log(arg1 + arg2);
}
add();

上記の例ではadd()を実行する際に引数を省略していますが、Default Parameterによる初期値が適用されています。

Template Strings

これまでJavaScriptでは文字列(Strings)を表すのにクォーテーションまたはダブルクォーテーションが用いられてきました。

ES6では、バッククォート(JISキーボードではShift+@)で文字列を囲むことができ、その内側では改行がソースコードのまま反映されたり、変数を展開することができるようになります。

ES5までは文字列を改行したり変数を文字列の間に差し込むためには、次のように記述していました。

var a = 5,
b = 10;
console.log('Fifteen is ' + (a + b) + 'andnnot' + (2 * a + b) + '.');

改行したい場所で「n」を用いるため、どこで改行されているのかも分かりづらいですし、いちいちクォーテーションを閉じて文字列結合するのも面倒ですね。

バッククォートで文字列を囲むES6の書式なら、次のように記述することができます。

var a = 5,
b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);

視覚的に分かりやすくなりますね。

Tagged template strings

バッククォートの手前に関数名を記述すると、Template Stringsとしているバッククォートの内側の文字列を引数として関数を実行することができます。

function fn() {
  return 'hoge';
}

console.log(`a${1+1}b${2+2}c${3+3}`);
console.log(fn`a${1+1}b${2+2}c${3+3}`);

上記の例では、上側のconsole.log()ではバッククォート「a2b4c6」とconsoleに出力されますが、下側のconsole.log()では「hoge」と出力されます。

これは下側のconsole.log()内でバッククォートの内側を引数として関数fn()が実行されており、関数fn()からの返り値として「hoge」が返されているため、バッククォートの内側が返り値である「hoge」に置き換わったためです。

引数ですが、第一引数には「${}」で囲んだ部分を除く文字列が配列として渡されます。
第二引数には1つ目の「${}」の中の値が、第三第四...と以降の引数には2つ目3つ目の「${}」...と続いていきます。

関数のほうでは、それらの値を使用しようと思うと全ての「${}」分の引数を用意しておかなくてはいけませんが、それは面倒ですね。
そこで、先述のRest Parameterを使用します。

function fn(strings, ...values) {
  console.log(strings);
  console.log(values);
  return 'hoge';
}

console.log(fn`a${1+1}b${2+2}c${3+3}`);

このように書くと、第一引数に「${}」を除く文字列の配列、第二引数に「${}」の中の値の配列というように渡すことができます。

関数が実行されるので、関数内に記述された「console.log(strings)」と「console.log(values)」が処理され、console内にそれぞれ出力され、fn()を実行している「console.log(fna${1+1}b${2+2}c${3+3})」では返り値「hoge」がconsoleに出力されます。

Raw strings

Tagged template stringsを用いて文字列を引数として渡す際、エスケープ記号などが考慮された値になっています。  

function r(strings, ...values) {
  console.log(strings[0]);
}
console.log(r`n`);

この書き方では、出力結果はタグインデントの空白文字だけが表示され、目に見える文字は何も表示されていないと思います。

これは「t」がタグインデントとして処理されたからですが、これを考慮せず単に「t」という文字列として表示したい場合にはRaw stringsを使うと便利です。

function r(strings, ...values) {
  console.log(strings.raw[0]);
}
console.log(r`t`);

ソースコードとしては「strings[0]」が「strings.raw[0]」に変わっただけです。

Tagged template stringsを用いるタグ関数(今回はr())の第一引数はrawプロパティを持っており、その中にエスケープ記号などを考慮していない、そのままの値が同様の配列で入っています。

Class

Classが使えるようになったことが、ES6のJavaScriptで最も注目されている部分ではないでしょうか。

これまで関数・クロージャ・プロトタイプなどを駆使してClassっぽいことをしていた部分において、JavaやC#に慣れ親しんだ人にも理解しやすい記述が可能になりました。

本記事の執筆時点では、まだデスクトップブラウザにClassが実装されていないため、動作確認することはできません。
ひとまず記述方法だけ書いておくと、次のような内容になっています。

// クラスの定義
class Greet {
  // コンストラクタ
  constructor(name) {
    // プロパティ
    this.name = name;
  }
  // インスタンスメソッド
  hello() {
    // 処理内容
    console.log('Hello, My name is ' + this.name)
  }
}

// インスタンスの作成
var tom = new Greet('Tom');

// インスタンスメソッドの実行
tom.hello();

Class内で定義されたプロパティやメソッドは、同じ関数内からであればthisからアクセスすることができます。

newでインスタンスを作成する際に渡した引数は、constructor()メソッドの引数として受け取ることができ、ここでプロパティに代入するなどの初期化処理を行います。

// インスタンスの作成
var tom = new Greet('Tom');

// インスタンスメソッドの実行
tom.hello();

Classを使ってインスタンスを作成している部分を見てみましょう。

これまでJavaScriptでもインスタンスという概念は存在しており、たとえば現在の日時を取得するためにnew Date()などでインスタンス化していました。

そのため、インスタンス化する感覚はこれまでとまったく同様で、インスタンスオブジェクトを変数などに代入して使用します。(var tom = new Greet('Tom')の部分)

インスタンス化した後はtom.hello()のようにオブジェクトのメソッドとしてClass内で定義しているインスタンスメソッドにアクセスできます。(tom.hello()の部分)

Classについては、GoogleChromeやFirefoxなどのデスクトップブラウザで実装された際に、JSFIDDLEやPlunkerなどで動作サンプルを作成して、改めて説明したいと思っています。

Classの継承

もちろん、Classを継承できるようになっています。

こちらのサンプルコードも特に意味はなく、「EventEmitterというClassが存在していると仮定」してClassの継承の解説に必要な内容のみ記述しました。

class Domain extends EventEmitter {
  constructor() {
    // 親Classのコンストラクタ呼び出し
    super();

    // 子Classで新たに定義するプロパティ
    this.members = [];
  }

  // インスタンスメソッド
  add(ee) {
    // 処理内容
    ee.domain = this;
    this.members.push(ee);
  }
}

Classを継承すれば、extendsの後に指定した親Class内で定義されているプロパティやインスタンスメソッドを、子Class内で再利用することができます。

感覚としては「親Classのコピーを作成したあと、子Class独自のカスタマイズを加える」と表現したほうが分かりやすいかもしれません。

その際、子Classのconstructor()内でsuper()を実行することが必要です。

単にextendsで親Classを呼び出すだけでなく、constructor()内のsuper()で親Class内のコンストラクタを呼び出し、プロパティを継承します。

2015年、JavaScriptは大きく変わる

ES6で新しい記述方法が増え、AngularJSがTypeScriptに移行するなど、JavaScriptを取り巻く環境は2015-2016年で大きく変動します。

ひとつ懸念しているのは、ES5までの書籍やWeb上の情報がずーっと残っていることです。
どこかの時点で体系的に最新のものをまとめてあげないと、これからJavaScriptに入門する人たちに大きな混乱をもたらす可能性があるのではないでしょうか。

実際のところ、ES5で書かれたコードが動かなくなるわけではないので、既に作られたソースも延々と残り続けるでしょうし、今後もES5以前のスタイルで新しいコードを書き続ける人たちもいるでしょう。

そのため、初心者から独学で習得していくのに、かなり時間のかかる言語になっていくような気がしています。