Skip to content

Latest commit

 

History

History
485 lines (358 loc) · 14.9 KB

es6.md

File metadata and controls

485 lines (358 loc) · 14.9 KB

ES2015 Frist Step

はじめに

JavaScriptECMAScriptという仕様のもとに実装されています。ECMAScript2015(ES2015)ECMAScriptの新しい標準仕様です。この仕様は2015年6月に確定しました。 ES5から6年ぶりのメジャーアップデートのため最初は違和感があるかもしれません。 しかし、より便利になるアップデートですのでしっかりキャッチアップし積極的に使用していきましょう。

ES6 or ES2015?

ネット上でES6ES2015を見かけると思いますが、両方とも同じものを指しています。正確にはES2015が正しいです。来年以降もES2016,ES2017…とアップデート予定です。当初はES6として仕様検討が始まったためその名残りです。

Babel

背景

ES2015の仕様確定が2015年6月にされましたが、すぐに新仕様が使えるわけではありません。JavaScriptエンジン(V8,JavaScriptCore,Chakra等)の実装は現在進行中でまだ使えない機能があるためです。こちらのサイトで各ブラウザに実装済みの機能が確認できます。

ブラウザとJavaScriptエンジンの対応は以下の表のとおりです。

ブラウザ JavaScriptエンジン
IE Chakra
Chrome V8
Opera V8
Safari JavaScriptCore
Firefox SpiderMonkey

Babel

ブラウザに新仕様が追加されるまで新仕様を導入することがきません。そこで登場したのがBabel(6to5)で、BabelES2015,ES NextのコードをES5のコードにトランスパイルしてくれるツールです(公式サイト)。ES2016,ES2017の様な次期仕様をES Nextと呼んでいます。ブラウザはトランスパイル後のJavaScriptコードを読み込むことになります。

ES2015新規仕様

ここでは特によく使われている仕様を説明します。

let, const

let, constはブロックスコープの変数宣言かできます。constは再代入不可な値を宣言します。これまで使われていたvarは使用しないようにしましょう。基本はconst、再代入が必要な場合のみletを使用するようにしましょう。

// ES5
var a = 1;

// ES2015
let b = 1; // 再代入不可能な値(再代入が必要なときのみ使用する)
const c = 1; // 再代入不可な値(推奨)

let, constvarとはスコープのルールが違います。

function varTest() {
  var x = 1;
  if (true) {
    var x = 2; // same variable! 
    console.log(x); // 2 
  } 
  console.log(x); // 2
} 

function letTest() { 
  let x = 1; 
  if (true) { 
    let x = 2; // different variable 
    console.log(x); // 2
  } 
  console.log(x); // 1 
}

これはvarがグローバルオブジェクトのpropertyとなるためです。(ブラウザの場合はwindow object)

var x = 'global';
let y = 'global';
console.log(this.x); // "global" 
console.log(this.y); // undefined

また、varとの違いとして、Temporal Dead Zone(TDZ)があります。変数宣言は、ブロックの先頭か、変数を使用する直前で宣言するようにしましょう。

{
  console.log(a); // undefined
  console.log(b); // Reference Error!

  var a;
  let b;
}

constは変更不可と説明しましたが、配列やオブジェクトの要素は変更が可能です。

{
  const a = [1,2,3];
  a.push(4);
  console.log(a); // 1,2,3,4

  a = 42; // Type Error!
}

配列やオブジェクトの要素も変更不可にするにはObject.freeze()immutable.js等を使うことになります。

Templete Strings

バッククォートを使った文字列の宣言ができます。メリットは文字列結合が簡単に書けることです。バッククォートで囲った文字列の${}は展開されます。またバッククォート内は改行できます。

// ES5
var errorCode = 404;
var errorMessage = `file not found.`;
console.error('Error!! Code: ' + errorCode + ', Message: ' + errorMessage);
// Error!! Code: 404, Message: file not found.

// ES2015
const errorCode = 404;
const errorMessage = `file not found.`;
console.error(`Error!! Code: ${errorCode}, Message: ${errorMessage}`);
// Error!! Code: 404, Message: file not found.

Class

クラスの宣言ができます。extendsによる継承や、instance objectが生成直後に実行されるconstructorが使用できます。

class User {
  constructor(name) {
    this.name = name;
  }

  say() {
    return `My name is ${this.name}`;
  }
}

// Userクラスを継承してAdminクラスを作る
class Admin extends User {
  say() {
    return `[Administrator] ${super.say()}`;
  }
}

const user = new User('Alice');
console.log(user.say()); // My name is Alice

const admin = new Admin('Bob');
console.log(admin.say()); // [Administrator] My name is Bob

Default Parameters

関数の引数にデフォルト値を設定できます。

// ES5
function consoleName(name) {
  name = (name != undefined) ? name : 'Taro';
  console.log('username: ' + name);
}

// ES2015
function consoleName(name = 'Taro') {
  console.log(`username: ${name}`);
}

consoleName(); // Taro
consoleName('Masaya'); // Masaya

Arrow Function

functionを=>に置換できます。

// ES5
$('.hoge').on('change', function(event) {
  console.log(event);
});

// ES2015
$('.hoge').on('change', (event) => {
  console.log(event);
});
// 中身が式なら`{}`を省略可能
$('.hoge').on('change', (event) => console.log(event));

// 式で書いた場合は、結果がreturnされます
const multify = (val) => val * val;
console.log(multify(5)); // 25

※注意点

thisの扱いがfunction=>で異なります。

class StatusCode {
  constructor(statusCode) {
    this.statusCode = statusCode;

    setTimeout(() => {
      console.log(this.statusCode, 'OK');
    }, 1000);

    setTimeout(() => console.log(this.statusCode, 'OK'), 1000);

    const self = this;
    setTimeout(function() {
      if (this.statusCode === undefined) console.log('`this.statusCode` is `undefined` in function');
      console.log(self.statusCode, 'OK');
    }, 1000);
  }
}

const statusCode = new StatusCode(200);

Allow Function内のthisStatusCodeクラスのthisを参照するのに対し、function()はグローバオブジェクトのthisを参照している。グローバルオブジェクトのthisstatusCodeは宣言していないのでundefinedとなります。

Enhanced Object Literals

オブジェクトリテラルが拡張されました。

// ES5
var obj = { "name": "Masaya Kamakura" };

// ES2015
// key名とvalueの変数名が同じであれば省略可能
function getNameObject(name) {
  return { name };
}
console.log(getNameObject('Masaya Kamakura')); // {"name":"Masaya Kamakura"}

// key名を動的に宣言可能
function getNameObject(name) {
  const nameKey = 'fullName';
  return { [nameKey]: name }; // keyに変数を使用
}
console.log(getNameObject('Masaya Kamakura')); // {"fullName":"Masaya Kamakura"}

function getNameObject(name) {
  return { [(() => 'fullName')()]: name }; // keyに関数を使用
}
console.log(getNameObject('Masaya Kamakura')); // {"fullName":"Masaya Kamakura"}

String Object APIs

APIがいくつか追加されました。ここではincludesrepeatstartsWithを紹介します。その他の機能はこちらを参考にしてください。

"abcde".includes("cd") // true
"abcde".includes("dd") // false

"abc".repeat(3) // "abcabcabc"

"abcde".startsWith("abc") // true
"abcde".startsWith("de") // false
  • includes()

    • 引数に指定した文字列が含まれていればtrue、以外はfalseを返す
  • repeat()

    • 引数に指定した回数だけ繰り返した文字列を返す
  • startsWith()

    • 引数に指定した文字列で開始していればtrue、以外はfalseを返す

Spread

配列を展開して返します。 配列に要素を追加して、新しい配列を作るときによく利用されます。

const list = [1, 2, 3];
console.log(...list); // 1 2 3

// 先頭に要素を追加する
const newList = [0, ...list];
console.log(newList); // [0, 1, 2, 3]

// Default Parametersと組み合わせて使う
function foo(x, y, ...z) {
  console.log(x, y, z);
}
foo(1, 2, 3, 4, 5); // 1 2 [3,4,5]

Import/Export

  • import: 外部モジュールを読み込む
  • export: 外部モジュールで利用できるようにする
// a.js
import * as fuga from "b"; // exportされたすべてのモジュールを読み込む
import { hoge1, hoge2 } from "b"; // hoge1, hoge2を読み込む
import foo from "b"; // `{}`をつけないとexport defaultされたモジュールを読み込む
// b.js
export default function foo() {};
export function hoge1() {};
export function hoge2() {};

Object.assign(target, ...sources)

1つ以上のsourceオブジェクトの保有する全てのプロパティをtargetにコピーします。戻り値はtargetオブジェクトになります。

const todo = {
  id: 1,
  text: 'catch up es6',
  status: 'pending'
};

const newTodo = Object.assign({}, todo, {status: 'progress'});
console.log(newTodo);
// { "id": 1, "text": "catch up es6", "status": "progress" }

Promise

callbackのような非同期プログラミングで使用する。

callbackを使った例

getAsync("fileA.txt", (error, result) => {
  if(error){// 取得失敗時の処理
    throw error;
  }
  // 取得成功の処理
});

Promiseを使った例

var promise = getAsyncPromise("fileA.txt"); // textを取得してPromise Objectを返す
promise.then((result) => {
  // 取得成功の処理
}).catch((error) => {
  // 取得失敗時の処理
});

getAsyncPromise()Promise Objectを返すfunctionです。処理が成功した場合then()。失敗した場合catch()が実行されます。

次に、getAsyncPromise()の中身を見てみましょう。

function asyncFunction() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      if (false) { // この例では実行させない
        reject(new Error('ERROR!'));
      }
      resolve('Async Hello world');
    }, 16);
  });
}

PromiseにはPromise.all(),Promise.race()という機能が用意されています。どちらも並列に非同期処理を実行させることができます。違いはthen,catchが実行されるタイミングです。

// Promise.all()のthen()はすべての処理が完了(resolve/reject)したら実行される
Promise.all([getAsyncPromise(url1), getAsyncPromise(url2), getAsyncPromise(url3)])
  .then(results => console.log(results))
  .catch(error => console.log(error));

// Promise.race()のthen()はどれか一つでも完了(resolve/reject)したら実行される(他の処理が中断されることではない)
Promise.race([getAsyncPromise(url1), getAsyncPromise(url2), getAsyncPromise(url3)])
  .then(results => console.log(results))
  .cache(error => console.log(error)); 

このような機能のおかげでcallback地獄になりにくいコード書くことができます。上記のPromise.all()の例をcallbackで書くこと以下のようになります。(error処理は省略)

asyncFunction().then(function (value) {
  console.log(value); // 'Async Hello world'
}).catch(function (error) {
  console.log(error); // reject()が実行された場合はこの行が処理される
});

※補足 Promise以外にもgenerator(ES2015)aync function(ES Next)のような非同期プログラミングをするための機能があります。

おまけ:ES5で積極的に使ってほしい機能

Array.prototype.forEach()

与えられた関数を、配列の各要素に対して一度ずつ実行します。

const data = [1, 2, 3, 4, 5];
data.forEach((val) => console.log(val));

for文だとループ変数が必要であったり、階層が深くなると可読性が下がるので使用しないようにしましょう。

Array.prototype.map()

与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。 Array.prototype.forEach()と似ていますが、新しい配列を生成するところに違いがあります。

const data = [1, 2, 3, 4, 5];
const square = data.map((val) => val * val);
console.log(square); // [1, 4, 9, 16, 25]

Array.prototype.filter()

引数として与えられたテスト関数を各配列要素に対して実行し、それに合格したすべての配列要素からなる新しい配列を生成します。

const data = [1, 2, 3, 4, 5];
// 要素が4未満の場合のみ2乗した結果を返す
const filterSquare = data.filter((val) => val < 4)
                         .map((val) => val * val);
console.log(filterSquare); // [1, 4, 9]

Array.prototype.reduce()

隣り合う 2 つの配列要素に対して(左から右へ)同時に関数を適用し、単一の値にします。

const data = [1, 2, 3, 4, 5];

const sum = data.reduce((pre, current) => pre + current);
console.log(sum); // 15

const max = data.reduce((pre, current) => Math.max(pre, current));
console.log(max); // 5

sumの例

pre current return
1st 1 2 3
2nd 3 3 6
3rd 6 4 10
4th 10 5 15

結果: 15

maxの例

pre current return
1st 1 2 2
2nd 2 3 3
3rd 3 4 4
4th 4 5 5

結果: 5

おわりに

この資料で紹介した機能はES2015の仕様でもごく一部です。さらに勉強したい方のために参考資料を残しておきます。 またES5で便利な関数も紹介しました。for,if,switch等を使わなくても書けることが多いので、ES5の機能で書けないか考えるようにしましょう。

参考資料