Help us understand the problem. What is going on with this article?

ES2015(ES6) 入門

はじめに

今更ですが、ES2015(ES6)で追加された機能や構文に関する備忘録です。
「JSは書けるけどES2015(ES6)はわからないっす...!」といった人達向けの記事です。
入門記事のためイテレータやジェネレータ等のわかりづらかったり、
説明が長くなりそうな機能や構文は割愛しております。

ES2015(ES6)とは

ECMASCriptの6th Editionのこと。ECMAScript 6th editionの6を取ってES6と呼ばれていたが、
2015年に標準化されたため正式名称はES2015になった。

正式名称がES2015ならES6という名称を使うのは間違いなのか

どちらの名称でも問題はない。
ES6の名称の方がエンジニアコミュニティに中では浸透しているらしく、ES6と記載されていることが多い。

詳細は以下を参考。
ES6 or ES2015 ? 〜WEB+DB PRESS Vol.87 ES6特集に寄せて〜

正式名称はES2015のため本記事ではES2015と表記する。

ES2015の対応状況

以下は各ブラウザのES2015の対応状況(ES2015のコードが動くかどうか)。
ECMAScript 6 compatibility table

全てのブラウザで対応しているわけではないため、Babel等のトランスパイラを利用して
ES2015で書いたコードを各ブラウザで動くコードであるES5のコードにトランスパイル(変換)する必要がある。

記事によっては「BabelはコンパイラでES2015をコンパイルするもの」と書かれていることがあるが、
どちらの言い方でも良い。

詳細は以下を参考(自分が調べて自己解決した記事です)。
Babelは「ES2015をコンパイルするコンパイラ」なのか、それとも「ES2015をトランスパイルするトランスパイラ」 なのか

何故ES2015で書くのか

  • 便利な機能、構文が追加され、従来より簡潔かつ明瞭な構文で記述できるようになったから。
  • 主要な機能、構文を覚える程度なら学習コストはそこまで高くないから。
  • フレームワーク等と違ってすぐに廃れるものではないから。
  • ES2015で記述されているコードを理解するため。

ES2015で追加された機能、構文

letconst

letconstで変数を宣言できる。
let再宣言が不可能な宣言であり、const再宣言と再代入が不可能な宣言。
間違えて変数を二重に定義しまったり、予期せぬ再代入を防ぐことができる。

従来のvarでの変数宣言

var name = 'soarflat';
console.log(name); // => soarflat


name = 'SoarFlat'; // 再代入する
console.log(name); // => SoarFlat

var name = 'SOARFLAT'; // 再宣言をする
console.log(name); // => SOARFLAT

letでの変数宣言

let name = 'soarflat';
console.log(name); // => soarflat


name = 'SoarFlat'; // 再代入する
console.log(name); // => SoarFlat

let name = 'SOARFLAT'; // 再宣言するとエラーになる

constでの変数宣言

const NAME = 'soarflat';
console.log(NAME); // => soarflat

NAME = 'SOARFLAT'; // 再代入するとエラーになる

letconstの使い分け

再代入が必要な変数のみletで宣言し、それ以外はconstで宣言する。
varは利用する理由がないため利用しない。

カーリーブラケット{}によるブロックスコープ

カーリーブラケット{}によるブロックスコープが有効。
ブラケット内にletconstで変数を宣言することによって、そのブラケット内のみで変数が有効になる。
変数の影響範囲を狭めることができる。

if (true) {
    var a = 1;
    let b = 2;
    const c = 3;
    console.log(a); // => 1
    console.log(b); // => 2
    console.log(c); // => 3
}

console.log(a); // => 1
console.log(b); // => undefined
console.log(c); // => undefined

アロー関数

アロー関数は無名関数の省略形の関数。
従来より簡潔な記述で関数を定義できる。

従来の無名関数

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

アロー関数

const fn = (a, b) => {
  return a + b;
};

// 単一式の場合はブラケットやreturnを省略できる
const fn = (a, b) => a + b;

// ブラケットやreturnを省略してオブジェクトを返したい場合は`()`で囲む
const fn = (a, b) => ({ sum: a + b });

従来の無名関数とthisの参照が異なる

従来の無名関数は関数の呼び方によってthisの参照先が異なるが、
アロー関数は、関数が定義されたスコープ内のthisを参照する。

以下はCounterコンストラクタのメンバ変数this.countの加算を期待した処理だが、
setTimeout関数内のthisがグローバルオブジェクトを参照しているため、意図しない処理になっている。

// グローバルオブジェクト
window.count = 10;

var Counter = function() {
  this.count = 0;
};

Counter.prototype.increment = function() {
  setTimeout(function(){
    // thisはグローバルオブジェクトを参照しているため、window.countに加算される。
    this.count++;
    console.log(this.count); // => 11
  }, 1000);
};

var counter = new Counter().increment();

そのため、以下のように別の変数にthisを参照させておく必要がある。

// グローバルオブジェクト
window.count = 10;

var Counter = function () {
  this.count = 0;
};

Counter.prototype.increment = function() {
  var self = this;
  setTimeout(function(){
    self.count++;
    console.log(self.count); // => 1
  }, 1000);
};

var counter = new Counter().increment();

アロー関数は関数が定義されたスコープ内のthisを参照するため期待通りの処理になる。
今回の場合はインスタンス自身を参照している。

const Counter = function () {
  this.count = 0;
};

Counter.prototype.increment = function() {
  setTimeout(() => {
    this.count++;
    console.log(this.count);  // 1
  }, 1000);
};

const counter = new Counter().increment();

Class構文

Class構文はprototypeベースのクラス定義構文の糖衣構文。
従来のprototypeベースの構文より、簡潔かつ明瞭な記述でクラスを定義できる。

prototypeベースの構文

var Person = function(name) {
  this.name = name;
};

Person.prototype.sayHello = function() {
  console.log("Hello, I'm " + this.getName());
};

Person.prototype.getName = function() {
  return this.name;
};

Class構文

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log("Hello, I'm " + this.getName());   
  }
  getName() {
    return this.name;
  }
}

extendsでクラスの継承

extendsでクラスの継承ができる。

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log("Hello, I'm " + this.getName());   
  }
  getName() {
    return this.name;
  }
}

// Personを敬承
// 同じメソッド名であるsayHelloは敬承先のものに上書きされる。
class Teacher extends Person {
  sayHello() {
    console.log("Hi, I'm " + this.getName());   
  }
}

const teacher = new Teacher('soarflat');
teacher.sayHello(); // => Hi, I'm soarflat

関数のデフォルト引数

関数にデフォルト引数を指定できる。

// 引数の値を乗算する関数
function multiply (a = 5) {
  return a * a;
}

console.log(multiply()) // => 25
console.log(multiply(10)) // => 100

分割代入

配列やオブジェクトからデータを取り出して個別の変数に代入できる。
従来より簡潔な記述で変数に代入できる。

従来の構文

var a = 1;
var b = 2;
var c = {d: 3};
var d = 3;

console.log(a, b, c, d); // => 1 2 {d: 3} 3

配列の分割代入の構文

const [a, b, c, d] = [1, 2, {d: 3}, 3];

console.log(a, b, c, d);  // => 1 2 {d: 3} 3

オブジェクトの分割代入の構文

const object = {a: 1, b: 2, c: {d: 3}};
const {a, b, c, c: {d}} = object;

console.log(a, b, c, d); // => 1 2 {d: 3} 3
const person = {
  name: 'soarflat',
  country: 'Japan'
};
const {name, country} = person;

console.log(name); // => soarflat
console.log(country); // => Japan
function fullName ({first, last}) {
  return first + last;
}

console.log(fullName({ first: 'John', last: 'Doe'})); // => JohnDoe

上記のような関数の引数に分割代入する構文は以下のメリットがある。

  • 引数の順番を考慮する必要がなくなるため、引数の追加や削除が楽。
  • 関数を実行する側は引数の順番を知らなくて良いので依存関係が減る。
  • 引数を渡す時に何を渡しているのかがわかりやすい。

テンプレート文字列

``(バッククオート)で文字列を囲むと、${}で文字列内に変数展開ができ、改行も反映できる。
従来の文字列での構文より簡潔かつ明瞭に記述できる。

従来の文字列での構文

const name = 'soarflat';

console.log('My name is ' + name); // => My name is soarflat
console.log("My name is " + name); // => My name is soarflat

テンプレート文字列での構文

const name = 'soarflat';

console.log(`My name is ${name}`); // => My name is soarflat
console.log(`My
name
is
${name}`); // => My
           //    name
           //    is
           //    soarflat

スプレッド構文...

スプレッド構文 は、複数の引数 (関数呼び出しのため) または複数の要素 (配列リテラルのため)、あるいは複数の値 (分割代入のため) が置かれるところで式が展開されます。

スプレッド構文 - JavaScript | MDN

スプレッド構文を利用すれば、従来の構文より簡潔に記述できる。
言葉だと説明しづらいため、実際に動作を見ていく。

可変長引数(レストパラメータ)

関数の仮引数にスプレッド構文を利用すると可変長引数を配列で受け取ることができる。

function func (...r) {
  console.log(r);
  console.log(r[0]);
}

func(1, 2, 3, 4, 5); // => [1, 2, 3, 4, 5]
                     // => 1
function func (a, ...r) {
  console.log(a);
  console.log(r);
}

func(1, 2, 3, 4, 5); // => 1
                     // => [2, 3, 4, 5]

配列の展開

スプレッド構文を利用すると配列の展開ができる。

従来の構文

var array = [1, 10, 3];

console.log(array);                       // => [1, 10, 3]
console.log(Math.max(1, 10, 3));          // => 10
console.log(Math.max(array));             // => NaN
console.log(Math.max.apply(null, array)); // => 10

スプレッド構文を利用した構文

const array = [1, 10, 3];

console.log(...array);           // => 1 10 3
console.log(Math.max(1, 10, 3)); // => 10
console.log(Math.max(array));    // => NaN
console.log(Math.max(...array)); // => 10
function func (x, y, z) {
  console.log(x);
  console.log(y);
  console.log(z);
}

const array = [1, 2, 3]
func(...array); // => 1
                // => 2
                // => 3

分割代入時に複数の値を1つの配列にまとめる

分割代入時にスプレッド構文を利用すると複数の値を1つの配列にまとめられる。

const [a, b, ...c] = ['a', 'b', 'c', 'C'];

console.log(a); // => a
console.log(b); // => b
console.log(c); // => ["c", "C"]

配列の結合

スプレッド構文を利用すると配列の結合が簡単にできる。

従来の構文

var array = [1, 2, 3];
var array2 = array.concat([4, 5, 6]);

console.log(array2); // => [1, 2, 3, 4, 5, 6]

スプレッド構文を利用した構文

const array = [1, 2, 3];
const array2 = [...array, 4, 5, 6];

console.log(array2); // => [1, 2, 3, 4, 5, 6]

for...of

反復可能なオブジェクト(配列等)に対して反復処理(ループ処理)ができる。
for...inは配列のインデックスやオブジェクトのプロパティ等を処理できるのに対し
for...ofは配列の値(要素)等を処理できる。

for...inを利用した構文

const array = [65, 55, 80, 100];

for(const index in array) {
  console.log(index);
}
// => 0
// => 1
// => 2
// => 3

for(const index in array) {
  console.log(array[index]);
}
// => 65
// => 55
// => 80
// => 100
const obj = {name: 'soarflat', country: 'Japan'}

for(const key in obj) {
  console.log(key);
}
// => name
// => country

for...ofを利用した構文

const array = [65, 55, 80, 100];

for(const value of array) {
  console.log(value);
}
// => 65
// => 55
// => 80
// => 100

for...ofでオブジェクトのプロパティの値は処理できないのか

通常ではできない。
従来通りにfor...inを利用する。

const obj = {name: 'soarflat', country: 'Japan'}

for(const key in obj) {
  console.log(obj[key]);
}
// => soarflat
// => Japan

Promise

Promiseは非同期処理を扱うオブジェクト。
Promiseを利用すれば従来より簡潔かつ明瞭に非同期処理を記述できる。

従来の構文(ネストが深くなりコールバック地獄と呼ばれている)

function timer(number, callback) {
  setTimeout(function() {
    callback(number * 2);
  },1000);
}

timer(100, function(number) {
  timer(number, function(number) {
    timer(number, function(number) {
      timer(number, function(number) {
        timer(number, function(number) {
          console.log(number); // => 3200
        });
      });
    });
  });
});

Promiseを利用した構文

function timer(number) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(number * 2);
    },1000);
  });
}

timer(100)
  .then(timer)
  .then(timer)
  .then(timer)
  .then(timer)
  .then(function onFulfilled(value) {
    console.log(value) // => 3200
  });

※ES2017の async/await を利用することで、非同期処理を更に簡潔に書けます。詳細は別の記事に書きましたので、こちらを参考にしてください。

async/await 入門(JavaScript)

終わり

上記の機能、構文は学習コストもそこまで高くはないため、触っていればすぐに慣れると思います。
いずれES2015で書こうと思っており、トランスパイルの面倒臭さよりES2015の魅力が勝っていると感じたなら
この瞬間からES2015を始めてみましょう。

以下を読めば、本記事では記載していない機能、構文に関して知ることができます。

お知らせ

KDP(Kindle ダイレクト・パブリッシング)を利用して、技術書を出版しました。

どちらも500円(Kindle Unlimited だったら無料)です。

書籍の詳細は商品ページからご覧いただけます。

興味を持ってくださった方はご購入いただけると大変嬉しいです。よろしくお願いいたします。

soarflat
フロントエンドエンジニア🐶
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした