ES2015(ECMAScript2015)の文法を覚える

JavaScriptについての知識は初心者から抜け出せない程度のレベルですが、フロントエンド界隈では現在キャッチアップする価値がある技術の1つとしてES2015が挙げられていたので、勉強したときのメモです。 ES2015の仕様全ては理解しきれていないので、今回は使用頻度の高そうな文法に絞って書いています。

参考:

ES2015とは

ES2015とは、JavaScriptが採用している標準化された言語仕様、ECMAScriptの第6版です。ES2015は2015年6月に発行され、現在(2016年4月)最新バージョンのブラウザでも全ての機能を実装できていないこともあり、Babel等のトランスパイラを用いてES5に変換して使用する方法が一般的です。変換方法については次回の記事で書きます。


letとconst

varのように変数宣言に使用します。上書きリスクが減ることから、今後はletとconstを主に使いそうです。 それぞれの違いは、

  再宣言 再代入 スコープ 巻き上げ
var できる できる 関数スコープ あり
let できない できる ブロックスコープ なし
const できない できない ブロックスコープ なし
var a = 0; // var宣言
let b = 1; // let宣言
const c = 2; // const宣言
// const d; // constは宣言のみはできない

var a = 3; // varは再宣言できる
// let b = 4; // letは再宣言できない
// const c = 5; // constは再宣言できない

a = 6; // varは再代入できる
b = 7; // letは再代入できる
// c = 8; // constは再代入できない

{
  var a = 9; // {}外で宣言したaを上書きする
  let b = 10; // {}内でのみ有効なbを宣言する
  const c = 11; // {}内でのみ有効なcを宣言する

  console.log(a); // 9
  console.log(b); // 10
  console.log(c); // 11
}

console.log(a); // 9
console.log(b); // 7
console.log(c); // 2


function以外の中括弧(ここではif)でもスコープが切れる。

// 今まで
function foo() {
  var bar = 1;
  if (true) {
    var bar = 2;
  }
  console.log(bar);
}
foo(); // 2 ifでスコープが切られないので上書きされる

// ES2015
function foo2() {
  let bar = 1;
  if (true) {
    let bar = 2;
  }
  console.log(bar);
}
foo2(); // 1 ifでスコープが切られるので上書きされない


巻き上げ(宣言よりも前で変数を参照すること)が発生しない。

// 今まで
console.log(e); // undefined 宣言前で変数を参照できてしまう
var e = 'hoge';

// ES2015
// console.log(f); // エラー
// let f = 'hoge';


for内でsetTimeoutが含まれる場合でも正しく動作する。

// 今まで
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 3が3回表示されてしまう
  }, 1000);
}

// ES2015
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 0,1,2 と表示される
  }, 1000);
}


アロー関数

function宣言を() =>という記述に省略できます。

// 今まで
var sum = function(a, b) {
  return a + b;
};

// ES2015
const sum = (a, b) => {
  return a + b;
};

// 関数本体が式だけで表せる場合、{}とreturnを省略できる
const sum = (a, b) => a + b;

// 引数が1つの場合、()を省略できる
const func = a => a * a;


さらに、アロー関数を使うとthisは自動的に補足されるため、setTimeout関数の引数に指定された無名関数の内部でthisがグローバルオブジェクトを指してしまう問題を回避することができます。

// 今まで
var takashi = {
  name: 'Takashi',
  sayHello: function() {
    var self = this; // thisをselfに保存しておく
    setTimeout(function() { // thisはグローバルオブジェクトを指す
      console.log("Hello, I'm " + self.name);
    }, 1000);
  }
}
takashi.sayHello(); // Hello, I'm Takashi

// ES2015
const takashi = {
  name: 'Takashi',
  sayHello() { // functionを省略できる(メソッド定義記法)
    setTimeout(() => { // アロー関数を使うとthisはtakashiを指す
      console.log("Hello, I'm " + this.name);
    }, 1000);
  }
}
takashi.sayHello(); // Hello, I'm Takashi


クラス

これまでJavaScriptにはクラスという概念がなく、prototypeを継承するなどして冗長な記述でクラスを定義していましたが、ES2015でクラスは正式な文法となりました。

// クラスの定義
class Person {
  constructor(name) { // コンストラクタ
   this.name = name; // プロパティ
  }

  sayHello() { // インスタンスメソッド
    console.log("Hello, I'm " + this.name);
  }

  static create(name) { // スタティックメソッド
    return new Person(name);
  }
}

// インスタンスの生成
const saki = new Person('Saki');
saki.sayHello(); // Hello, I'm Saki

// スタティックメソッドの呼び出し
const kota = Person.create('Kota');


クラスの継承はextendsを使い、継承元のコンストラクタメソッドを呼び出すためにはsuperを使います。

// クラスの継承
class Teacher extends Person {
  constructor(name, age) {
    super(name); // 継承元のコンストラクタを呼び出す
    this.age = age;
  }

  sayHello() {
    super.sayHello(); // 継承元のメソッドを呼び出す
    console.log(this.age + ' years old');
  }

  static create(name, age) { // スタティックメソッドの上書き
    return new Person(name, age);
  }
}

const ellen = new Teacher('Ellen', '28');
ellen.sayHello();


引数の拡張

関数に指定する引数のデフォルト値を設定することができるようになりました。

const sum = (a = 1, b = 2) => {
  return a + b;
};
console.log(sum()); // 3
console.log(sum(2)); // 4
console.log(sum(2,3)) // 5


さらに可変長の引数を取ることができるようになりました。(レストパラメータ)

const foo = (a,b,...rest) => {
  console.log('a:', a);
  console.log('b:', b);
  console.log('rest:', rest);
};
foo(1,2,3,4,5);
// a: 1
// b: 2
// rest: [3, 4, 5]


オブジェクト記法の拡張

様々な省略記法が可能になりました。 - オブジェクトのプロパティのキー名と値の変数名が同じ場合の省略記法

var foo = 1;

// 今まで
var obj = {'foo': foo};

// ES2015
const obj = {foo};
  • プロパティのキー名が変数に入った文字列である場合の省略記法
var key = 'foo';

// 今まで
var obj = {};
obj[key] = 1;
console.log(obj.foo); // 1

// ES2015
const obj = {
  [key]: 1
};
console.log(obj.foo); // 1
// 今まで
var obj = {
  add: function(a,b) {
    return a + b;
  }
};
console.log(obj.add(1,2)); // 3

// ES2015
const obj = {
  add(a,b) {
   return a + b;
  }
};
console.log(obj.add(1,2)); // 3

// ES2015 アロー関数を使う場合
const obj = {
  add: (a,b) => a + b
};
console.log(obj.add(1,2)); // 3


展開演算子(スプレッドオペレータ)

可変長の引数を配列として受け取るレストパラメータに対して、配列を複数の引数に展開する機能がスプレッドオペレータです。

var arr = [1, 3, 2];

// 今まで
var max = Math.max.apply(null, arr);
console.log(max); // 3

// ES2015
const max2 = Math.max(...arr);
console.log(max2); // 3


分割代入

分割代入は、配列やオブジェクトからデータを抽出できる機能です。

// 配列の分割代入
const week = ['月', '火', '水', '木', '金', '土', '日',];

const [a, b, c, d, e, f, g] = week;
console.log(a, b, c, d, e, f, g); // 月 火 水 木 金 土 日

// 2番目と5番目の値だけを代入
const [,b,,,e] = week;
console.log(b, e); // 火 金

// レストパラメータの活用
const [a, b,...cdefg] = week;
console.log(a, b, cdefg); // 月 火 ["水", "木", "金", "土", "日"]

// 値の交換
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

// オブジェクトの分割代入
const obj = {
  name: 'たかし',
  age: 15
};

const {name: a, age: b} = obj;
console.log(a, b); // たかし 15

// オブジェクト代入のプロパティ省略記法
const {name, age} = obj;
console.log(name, age); // たかし 15

// デフォルトの値の指定
const {name, age = 15} = {name: 'たかし'};
console.log(name, age); // たかし 15


イテレータ

イテレータとは、集合から要素を繰り返し取り出すためのオブジェクトです。現在の位置を記録しつつ、中の要素へ1つずつアクセスするnextメソッドを持っています。

const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();

console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: undefined, done: true}


for of文

反復処理可能なオブジェクトの値を列挙することができます。for inはプロパティ名を取得するのに対して、for ofはプロパティの値を取得します。

const arr = [3, 4, 5];
arr.foo = 'hoge';
console.log(arr);

for (let a in arr) {
  console.log(a); // 0 1 2 foo
}

for (let b of arr) {
  console.log(b); // 3 4 5
}