Skip to content

isdh/ES6-for-humans

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

80 Commits
 
 

Repository files navigation

ES6 for Humans


Table of Contents


他言語


1. let, const と ブロックスコープ

宣言文 let はブロックスコープと呼ばれるブロックを作ることが出来ます。 ES6では、関数スコープで使用していたvar の代わりに、letの使用が推奨されています。

var a = 2;
{
    let a = 3;
    console.log(a); // 3
    let a = 5; // TypeError: Identifier 'a' has already been declared
}
console.log(a); // 2

ブロックスコープにおけるもう一つの宣言文は const です。const は定数を生成します。 ES6のconstは、値への参照を示す事になります。言い換えれば、この値は凍結されるわけではなく、割り当てているだけなのです。例を見て下さい。

{
    const B = 5;
    B = 10; // TypeError: Assignment to constant variable

    const ARR = [5, 6];
    ARR.push(7);
    console.log(ARR); // [5,6,7]
    ARR = 10; // TypeError: Assignment to constant variable
    ARR[0] = 3; // 値は変更可能
    console.log(ARR); // [3,6,7]
}

頭の片隅に覚えていてほしいこと:

  • letconstのホイスティングは今までの変数、関数のホイスティングから様変わりしました。letconstもどちらも、巻き上げるが、その宣言の前にアクセスすることは出来ません。これは、Temporal Dead Zone(英語)のためです。
  • letconstのスコープは最も近い閉じたブロックになります。
  • constを使用する際は、大文字で記述して下さい(一般的な慣習でもあります)
  • constは宣言すると同時に定義しなければならなりません。

2. アロー関数

アロー関数はES6で関数を書く際の短縮表記のことです。アロー関数は=>に続く関数本体と、(...)で表される引数の一覧で定義します。

// クラシカルな関数式
let addition = function(a, b) {
    return a + b;
};

// アロー関数で実装
let addition = (a, b) => a + b;

上記の例に加えて、アロー関数ではreturn文を書く必要がありません。関数本体を簡潔に実装するためです。

これが通常のブロックで関数を記述した例です。

let arr = ['apple', 'banana', 'orange'];

let breakfast = arr.map(fruit => {
    return fruit + 's';
});

console.log(breakfast); // ['apples', 'bananas', 'oranges']

ちょっと待って! もう一つ...

アロー関数はコードそのものを短くするわけではありません。thisを束縛する行為と密接に関係しています。

アロー関数の動作は、thisの動きとともに通常の関数とは異なります。JavaScriptにおける其々の関数はthisの文脈を定義できます。しかし、アロー関数が捉えるthisは、閉じた文脈となります。次のコードを見てください。

function Person() {
    // Person()コンストラクターが定義する`this`はインスタンスそのものだ
    this.age = 0;

    setInterval(function growUp() {
        // strict mode ではない時、 grouUp() 関数は `this` を 
        // globalオブジェクトとして定義する。Person()コンストラクターが定義した`this`
        // とは異なる
        this.age++;
    }, 1000);
}
var p = new Person();

ES3,ES5において、以上の事案に対してはthisを変数に割り当てることで対応してきました。

function Person() {
    var self = this;
    self.age = 0;

    setInterval(function growUp() {
        // コールバックが参照する`self`変数は想定しているオブジェクトを指し示す
        self.age++;
    }, 1000);
}

上記から、アロー関数はthisの値を最も近い閉じた文脈を捉えることができるため、ネストしたアロー関数に対しても、以下のコードのように想定通りの動きをすることになります。

function Person() {
    this.age = 0;

    setInterval(() => {
        setTimeout(() => {
            this.age++; // `this` は適切にpersonオブジェクトを参照する
        }, 1000);
    }, 1000);
}

var p = new Person();

アロー関数内のLexical thisについてよく知りたければ参照


3. 関数のデフォルトパラメーター

ES6では関数定義時にでデフォルトパラメーターを設定することができます。実例をみてください。

let getFinalPrice = (price, tax = 0.7) => price + price * tax;
getFinalPrice(500); // 850

4. スプレッド演算子 /レスト演算子

... 演算子はスプレッド演算子、またはレスト演算子をとして動きます。使用の仕方によって動きが異なります。

イテラブルな何かと使用すると、...はスプレッド演算子として働く。

function foo(x, y, z) {
    console.log(x, y, z);
}

let arr = [1, 2, 3];
foo(...arr); // 1 2 3

...のもう一つのよく知られた使い方は値を集めて配列にすることだ。これはレスト演算子として参照される。

function foo(...args) {
    console.log(args);
}
foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

5. オブジェクトリテラルの拡張

ES6ではオブジェクトリテラルの宣言の際に、プロパティの初期化と関数メソッドの定義を短縮して記述することができます。これはオブジェクトリテラルの定義からプロパティのキーを計算することができるためです。

function getCar(make, model, value) {
    return {
        // プロパティの値を短縮して記述することで、
        // キーと変数名が一致したプロパティになります
        make,  // make: make   と同じ
        model, // model: model と同じ
        value, // value: value と同じ

        // computed values now work with
        // object literals
        // 記述された値はオブジェクトリテラルとして機能します。
        ['make' + make]: true,

        // メソッド定義の短縮形では`function`キーワードとコロンを省略することができます
        depreciate() {
            this.value -= 2500;
        }
    };
}

let car = getCar('Kia', 'Sorento', 40000);
console.log(car);
// {
//     make: 'Kia',
//     model:'Sorento',
//     value: 40000,
//     makeKia: true,
//     depreciate: function()
// }

6. 8進数と2進数リテラル

ES6では新たに8進数と2進数リテラルをサポートしました。 0o0Oで始まる number は8進数に変換されます。 以下のコードを見てください。

let oValue = 0o10;
console.log(oValue); // 8

let bValue = 0b10; // 0b or 0B for binary
console.log(bValue); // 2

7. 配列とオブジェクトの分解

分解によってオブジェクトと配列を扱う際に一時的な変数の使用を避けることができます。

function foo() {
    return [1, 2, 3];
}
let arr = foo(); // [1,2,3]

let [a, b, c] = foo();
console.log(a, b, c); // 1 2 3

function bar() {
    return {
        x: 4,
        y: 5,
        z: 6
    };
}
let { x: a, y: b, z: c } = bar();
console.log(a, b, c); // 4 5 6

8. オブジェクトにおけるsuperの使用

ES6ではsuper関数をプロトタイプと一緒に使用することを可能になりました。

var parent = {
    foo() {
        console.log("Hello from the Parent");
    }
}

var child = {
    foo() {
        super.foo();
        console.log("Hello from the Child");
    }
}

Object.setPrototypeOf(child, parent);
child.foo(); // Hello from the Parent
             // Hello from the Child

9. テンプレートリテラルとデリミタ

ES6では文字列への代入をかんたんにできます。代入したと同時に自動的に評価されます。

  • `${ ... }`は変数をレンダリングできる
  • ` \バックスラッシュはデリミタとして使用する
let user = 'Kevin';
console.log(`Hi ${user}!`); // Hi Kevin!

10. for...of vs for...in

  • for...of 配列のようなイテラブルなオブジェクトをイテレート(順繰りに処理)する
let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname of nicknames) {
    console.log(nickname);
}
// di
// boo
// punkeye
  • for...in はオブジェクトの数え上げる事ができるプロパティをイテレートする
let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname in nicknames) {
    console.log(nickname);
}
// 0
// 1
// 2
// size

11. Map と WeakMap

ES6には新しいデータ構造のMapWeakMapがあります。全てのオブジェクトは、Mapとも考えられるので、私たちはJavaScriptでいつもMapを使用しているといえます。

オブジェクトがキー(常に文字である)と値で出来ているのに対し、Mapでは、すべての値(オブジェクトでも、プリミティブな値でも!)をキーにも値にも使用することができるのです。コードを見て下さい。

var myMap = new Map();

var keyString = "a string",
    keyObj = {},
    keyFunc = () => {};

// 値をセットします
myMap.set(keyString, "'a string' に割り当てた値");
myMap.set(keyObj, "keyObj に割り当てた値");
myMap.set(keyFunc, "keyFunc に割り当てた値");

myMap.size; // 3

// 値を取得します
myMap.get(keyString);    // "'a string' に割り当てた値"
myMap.get(keyObj);       // "keyObj に割り当てた値"
myMap.get(keyFunc);      // "keyFunc に割り当てた値"

WeakMap

WeakMapはマップです。しかし、ガベージコレクトされる、弱い参照を持つキーになっています。つまり、WeakMapではメモリリークを心配する必要がありません。

また、Mapに対して、WeakMapでは、全てのキーはオブジェクトでなくてはなりません。

WeakMapはたった4つのメソッドしか持ちません。delete(key), has(key), get(key)set(key, value)です。

let w = new WeakMap();
w.set('a', 'b');
// Uncaught TypeError: Invalid value used as weak map key

var o1 = {},
    o2 = () => {},
    o3 = window;

w.set(o1, 37);
w.set(o2, "azerty");
w.set(o3, undefined);

w.get(o3); // undefined, because that is the set value

w.has(o1); // true
w.delete(o1);
w.has(o1); // false

12. Set and WeakSet

Set オブジェクトはユニークなコレクションです。重複した値を無視する、全てがユニークな値を保持するコレクションです。Setにはプリミティブ値でも、オブジェクトでも格納できます。

let mySet = new Set([1, 1, 2, 2, 3, 3]);
mySet.size; // 3
mySet.has(1); // true
mySet.add('strings');
mySet.add({ a: 1, b:2 });

forEachメソッド、またはfor...ofループを使用して、Setをイテレートし、順番にアクセスすることが出来ます。

mySet.forEach((item) => {
    console.log(item);
    // 1
    // 2
    // 3
    // 'strings'
    // Object { a: 1, b: 2 }
});

for (let value of mySet) {
    console.log(value);
    // 1
    // 2
    // 3
    // 'strings'
    // Object { a: 1, b: 2 }
}

また、Setはdelete()メソッドとclear()メソッドを保持します。

WeakSet

WeakMapと同様に、WeakSetは弱いオブジェクトへの参照を持つコレクションです。WeakSetはユニークなオブジェクトを持つコレクションであるため、WeakSet内にはただ一度だけ現れます。

var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false, fooはsetにまだ加えられていない

ws.delete(window); // setからwindowを取り除く
ws.has(window);    // false, windowを取り除いた

13. Classes in ES6

ES6にはクラスのシンタックスが新しく追加されました。注意すべきことは、ES6のクラスはオブジェクト指向の継承モデルではないということです。あくまで既存のプロトタイプベースのJavaScriptのシンタックスシュガー(糖衣構文)に過ぎません。

ES6のクラスは、新しいシンタックスです。今まで使用していたES5のprototype,constructor関数が内部で働きます。

staticキーワードをもちいることで静的関数をクラス内に定義できます。

class Task {
    constructor() {
        console.log("task instantiated!");
    }
    
    showId() {
        console.log(23);
    }
    
    static loadAll() {
        console.log("Loading all tasks..");
    }
}

console.log(typeof Task); // function
let task = new Task(); // "task instantiated!"
task.showId(); // 23
Task.loadAll(); // "Loading all tasks.."

クラスにおけるextendsとsuper

次のコードを見て下さい。

class Car {
    constructor() {
        console.log("Creating a new car");
    }
}

class Porsche extends Car {
    constructor() {
        super();
        console.log("Creating Porsche");
    }
}

let c = new Porsche();
// Creating a new car
// Creating Porsche

extendsはES6において、親クラスから子クラスへの継承を可能にします。コンストラクターでsuperを呼び出すことを忘れないでください。

また、親クラスのメソッドを子クラスからsuper.parentMethodName()といった形で使用できます。

クラスについてもっと知りたい

とどめておくべきこと

*クラス宣言はホスティングをしません。はじめにクラスを宣言してからアクセスできます。そうでないとReferenceErrorが投げられます。 *クラス宣言の内部で関数を定義するときに、functionキーワードは使用する必要はありません。


14. Symbol

シンボル(Symbol)は値を変更出来ないデータ型です。シンボルの目的は、ユニークな識別子を生成することです。

シンボルを作成してみましょう。

var sym = Symbol("some optional description");
console.log(typeof sym); // symbol

Symbol(…)を使用する際は、newキーワードは不要です。

シンボルはオブジェクトのプロパティ、またはキーとして使用された時、通常の数え上げられるプロパティとしては格納されません。

var o = {
    val: 10,
    [Symbol("random")]: "I'm a symbol",
};

console.log(Object.getOwnPropertyNames(o)); // val

オブジェクトのシンボルとなったプロパティにアクセスするときは、Object.getOwnPropertySymbols(o)を使用して下さい。


15. Iterators

イテレータはコレクションの1つのアイテムに一度アクセスします。そして、コレクションのアクセスした場所を記憶しています。 イテレータにはコレクションの次の順番のアイテムを返すnext()メソッドがあり、返却されたアイテムであるオブジェクトは、doneとvalueという2つのプロパティを持っています。

ES6はSymbol.iteratorをもち、オブジェクトのためのイテレータを提供します。オブジェクトはいつでも、for...ofのようなループで数え上げる事ができます。オブジェクトの@@iteratorメソッドは実行時に引数を持たず、イテレートすることで値を返すイテレータを返します。

配列を見てみましょう。配列はイテラブル(数え上げ可能)です。イテレータは値を出力します。

var arr = [11,12,13];
var itr = arr[Symbol.iterator]();

itr.next(); // { value: 11, done: false }
itr.next(); // { value: 12, done: false }
itr.next(); // { value: 13, done: false }

itr.next(); // { value: undefined, done: true }

オブジェクトの定義の際にobj[Symbol.iterator]() を定義することで、自前のイテレータを記述することもできます。


16. Generators

ジェネレータ関数はES6の新しい特徴の1つです。ジェネレータ関数は何度も値を返します。返り値となるオブジェクトをイテレートし、1度のイテレートで1つ、値を取得します。

ジェネレータ関数は実行された際に、イテラブルなオブジェクトを返します。 *という新しいシンタックスとyieldというES6の新しいキーワードによって記述されます。

function *infiniteNumbers() {
    var n = 1;
    while (true) {
        yield n++;
    }
}

var numbers = infiniteNumbers(); // returns an iterable object

numbers.next(); // { value: 1, done: false }
numbers.next(); // { value: 2, done: false }
numbers.next(); // { value: 3, done: false }

yidldを呼んだ回数分だけ、yieldされた値が次の値になります。

また、ジェネレータはオンデマンドでyieldされた値を計算し、効率よく、連続したシークエンスに要求に応じてアクセスできます。無限に続くシークエンスですら可能です。


17. Promises

ES6ではネイティブでPromiseをサポートしています。プロミスは非同期実行が完了することを待ち続けるオブジェクトです。非同期実行が完了したとき、プロミス(つまり約束)は「遂行(fulfilled/resolved)」されるか、「破棄(rejected)」されます。

Promiseをつくる通常の方法は、new Promise()コンストラクターを使用することで、2つの与えられた関数を引数として扱える様になります。第一引数はresolveと呼ばれる関数で、future valueとともに 実行されることが想定されます。第二引数はrejectで、Promiseがfuture valueをresolveせず、rejectされたときに実行されます。

var p = new Promise((resolve, reject) => {  
    if (/* condition */) {
        resolve(/* value */);  // fulfilled successfully
    } else {
        reject(/* reason */);  // error, rejected
    }
});

全てのPromiseはthenと呼ばれるメソッドをもっています。このメソッドはペアとなるコールバックを保持します。 1つめのコールバックはプロミスが遂行された際に、もう一方はプロミスが破棄された時に呼ばれます。

p.then((val) => console.log("Promise Resolved", val),
       (err) => console.log("Promise Rejected", err));

thenのコールバックから返却された値は次のthenのコールバックに渡されます。

var hello = new Promise((resolve, reject) => { resolve("Hello") });

hello.then((str) => `${str} World`)
     .then((str) => `${str}!`)
     .then((str) => console.log(str)) // Hello World!

Promiseが帰ってきた時、遂行された値は次のコールバックにチェーンして渡していきます。 これはコールバック地獄を避けるシンプルな1つの策です。。

var p = new Promise((resolve, reject) => { resolve(1) });

var eventuallyAdd1 = (val) => new Promise((resolve, reject) => { resolve(val + 1) });

p.then(eventuallyAdd1)
 .then(eventuallyAdd1)
 .then((val) => console.log(val)); // 3

About

A Kickstarter guide to writing ES6 in Japanese

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published