hello-world
webエンジニアのメモ。とりあえずやってみる。

ES6(ES2015)の基本文法を学ぶ

公開日時

以前jaws-frameworkで作成したAPIがあるのですが、 serverlessが登場したので移行を兼ねてES6で書きなおしてみようと思っています。

とはいえES6のことがよく分かっていないので『WEB+DB PRESS Vol.87』に掲載されていた「今すぐ活かす! 最新JavaScript」特集を読みながらES6の基本を学ぶことにしました。

すぐ忘れてしまうので試したことのメモを残しておきます。

ES6をブラウザで試す

BabelのTry it outページで試せます。

右側の画面にコンパイル後のコードが表示されるので、どういう変換が行われているのかが分かりやすいです。

アロー関数

  • 関数本体が式だけで表せる場合、ブロックの波括弧とreturnを省略可能

  • Arrayのfilterメソッドやmapメソッドなど、関数の引数としてアロー関数を使うと効果的

// es6
[1,2,3,4].filter(n => n % 2 === 0).map(n => n * n)

var add = (a, b) => {
  return a + b;
}

var add = (a, b) => a + b;

var square = n => n * n

// es5
[1, 2, 3, 4].filter(function (n) {
  return n % 2 === 0;
}).map(function (n) {
  return n * n;
});

var add = function add(a, b) {
  return a + b;
};

var add = function add(a, b) {
  return a + b;
};

var square = function square(n) {
  return n * n;
};
  • thisの捕捉について

    • アロー関数は単なる省略記法ではなく、アロー関数を定義したコンテキストでのthisを捕捉する機能がある

    • var self = this; を書かなくてよくなる

    • functionとの使い分け

    • 意図的にthisを束縛している箇所では注意が必要

    • ES6以降のAPI設計では、アロー関数との組み合わせを考慮して、データをthisに束縛するよりも引数として渡す方が好まれるようになっていく(だろう)

// es6
var john = {
  name: "John",
  helloLater: function() {
    setTimeout(() => {
      console.log(`Hello, I'm ${this.name}'`);
    }, 1000);
  }
}

// es5
var john = {
  name: "John",
  helloLater: function helloLater() {
    var self = this;
    setTimeout(function () {
      console.log("Hello, I'm " + self.name + "'");
    }, 1000);
  }
};

クラス

  • ES5でのクラスの実体はただの関数であるため、newを書き忘れてしまうミスがよくあった

  • ES6クラスはnew以外で呼び出すとエラーになるためミスにすぐ気づくことができる

  • ES6クラスにはJavaのようなpublic、privateなどのアクセス修飾子や抽象クラスはなく、シンプルな仕様になっている

  • 組込みクラスが継承可能になった

    • これまではErrorやArrayなどの組込みクラスを継承する一貫した方法はなかった
// es6
class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }

  static create(name) {
    return new Person(name);
  }
}

// extends
class Author extends Person {
  constructor(name, book) {
    super(name)
    this.book = book;
  }

  // override
  greet() {
    super.greet();
    console.log(`I wrote ${this.book}`);
  }

  // override
  static create(name, book) {
    return new Author(name, book);
  }
}

var bob = new Person("Bob");
bob.greet();
// Hello, I'm Bob

var author = new Author("Gillian", "Gone Girl");
author.greet();
// Hello, I'm Gillian
// I wrote Gone Girl

他の言語と似たclass定義になりましたね。

  • 継承時に親クラスのコンストラクタが必ず呼ばれる
// 子クラスのコンストラクタを省略した場合は以下が暗黙的に定義される
constructor(...args) {
  super(...args);
}

オブプロジェクトリテラル拡張

プロパティ省略記法

  • オブジェクトのプロパティのキー名と値の変数名が等しい場合、ES6では省略記法を使えるようになった
// es6
var foo = 0, bar = 1;
var obj = {foo, bar}

// es5
var foo = 0, bar = 1;
var obj = { foo: foo, bar: bar };

コンピューテッドプロパティ

  • 従来はプロパティのキー名が変数に入った文字列であるとき、一度オブジェクトを生成する必要がったが、ES6ではキーを角括弧で囲むことで直接変数や式を指定できるようになった
// es6
var key = 'foo';
var obj = {
  [key]: 0,
  [key + "_bar"]: 1,
};

// es5
var key = 'foo';
var obj = {};
obj[key] = 0;
obj[key + "_bar"] = 1;

メソッド定義

  • functionキーワードを使わず短く定義できる記法が追加された
// es6
var counter = {
  count: 0,
  increment() {
    return this.count++;
  }
}

// es5
var counter = {
  count: 0,
  increment: function increment() {
    return this.count++;
  }
};

ブロックスコープ

  • 従来のjsのスコープは原則として関数スコープだった

  • es6ではブロックスコープの変数を宣言するletが導入された

  • 直感的に書けるようになった

// es6
for(let i = 0; i < 5; i++) {
  // iはブロックスコープごとに保存される
  setTimeout(() => console.log(i), i * 100);
}

// es5
// よくある間違い
for(var i = 0; i < 5; i++) {
  setTimeout(function() { console.log(i) }, i * 100);
}
// => console.logが実行されるのはループが周り、5が5回表示されてしまう

// 正しく動作するコード
for(var i = 0; i < 5; i++) {
  // 関数スコープとして変数を保存
  (function(x) {
    setTimeout(function() { console.log(x) }, i * 100);
  })(i);
}

久しぶりにjsを書くと必ずハマる関数スコープ問題もletで解決ですね。

  • constでブロックスコープの定数宣言

    • オブジェクトの変更を禁止するわけではない
const foo = 1;
foo = 100; // error

const obj = {};
obj.foo = 1; // 変更は可能

関数パラメータの拡張

デフォルトパラメータ

  • 関数の引数のデフォルト値を指定できるようになった
var add = (a = 1, b = 2) => a + b;

レストパラメータ

  • ...に続けて書いた仮引数は、引数を配列として受け取ることができる(可変長引数)
function foo(first, second, ...rest) {
  console.log(first);
  console.log(second);
  console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 1
// 2
// [3,4,5]

スプレッドオペレータ

  • 配列を複数の引数に展開する機能
// es6
var arr1 = [1, 2];
var arr2 = [3, 4, ...arr1];

// es5
var arr1 = [1, 2];
var arr2 = [3, 4].concat(arr1);

分割代入

変数への代入

// es6
var regex = /(\d{4})(\d{2})(\d{2})/;
var [, year, month, day] = regex.exec("20151231");
console.log(year, month, day);

// es5
var regex = /(\d{4})(\d{2})(\d{2})/;
var date = regex.exec("20151231");
var year = date[1];
var month = date[2];
var day = date[3];
console.log(year, month, day);

一時変数を書かなくてよくなるのでコードが短くなりますね。

// オブジェクトの分割代入
var {name: a, age: b} = {name: "Bob", age: 20};
console.log(a, b);

// プロパティ省略記法
var {name, age} = {name: "Bob", age: 20};
console.log(name, age);

// デフォルト値の指定
var {name, age = 18} = {name: "Bob"};
console.log(name, age);

// ネストしたオブジェクトからの抽出
var {foo: {bar: [, x]}} = {foo: {bar: [1, 2, 3]}};
console.log(x);

ネストしたオブジェクトからの抽出はよく使うのでしょうか?

パッと見でどれが変数なのかが分かりづらくなってしまうのではないかと思いました。

関数引数の分割代入

  • 擬似的にrubyのキーワード引数のような関数の使い方が可能
function foo({a = 0, b = 0} = {}) {
  console.log(a, b);
}
foo();
foo({a: 1});

イテレータ

for/of文

var iter = [1, 2, 3];
for (let n of iter) {
  console.log(n);
}

// 1
// 2
// 3

var iter = "foo";
for (let n of iter) {
  console.log(n); // 
}

// f
// o
// o

テンプレートリテラル

バッククォート

var name = "Bob";
console.log(`Hello, ${name}.`)

複数行文字列

  • ヒアドキュメントと異なり先頭行や行頭の空白は無視されない
var text = 
`line 1
line 2
line 3`;
console.log(text);

Map、Set、WeakMap、WeakSet

Map

  • key-value型のデータを扱う専用クラス
var map = new Map();
map.set('key1', 'value1');
console.log(map.get('key1'));
console.log(map.has('key1'));
map.delete('key1');
console.log(map.has('key1'));

// コンストラクタに[key, value]のイテレータを渡すと一括指定が可能
var map2 = new Map([['k1', 'v1'], ['k2', 'v2']]);
console.log(map2.size);

for(let [k, v] of map2) {
  console.log(k, v);
}

map2.clear();
console.log(map2.size);

Set

  • 重複がないユニークな値の集合を表すデータ型
var set = new Set(["v1", "v2", "v1"]);
for (let v of set) {
  console.log(v); // v1, v2
}

WeakMapとWeakSet

  • キーにオブジェクトを指定できるようになったことでオブジェクトがGCの対象にならずメモリリークを引き起こす可能性がある

  • キーに指定されたオブジェクトに対して弱い参照を持つことでGCを妨げないWeakMapとWeakSetが導入された

  • 使い方はMapやSetとほぼ同じだが、イテレータを伴うメソッドやclearメソッドが存在しない

  • イテレータがないということは、キーを知らないと値にアクセスできないということ

  • これを利用して、WeakMap をキャッシュに利用したり、プライベートプロパティを実現できる

Stringに便利メソッドの追加

console.log("abcde".startsWith("ab"));  // true
console.log("abcde".endsWith("de"));    // true
console.log("abcde".includes("bcd"));   // true
console.log("abc".repeat(3));           // abcabcabc

以上、ところどころ飛ばした部分もあったのですが、ES6の基本文法をざっと学ぶことができました。

WEB+DB PRESS Vol.87』の記事がとても分かりやすかったです。

es6だと全体的にシンプルに書けて良いですね。


Related #es6

[js]Promiseについて学ぶ

昨日の ES6(ES2015)の基本文法を学ぶの続きで、今回はPromiseについて学びました。