let
,const
と ブロックスコープ- アロー関数
- 関数のデフォルトパラメーター
- スプレッド演算子 /レスト演算子
- オブジェクトリテラルの拡張
- 8進数と2進数リテラル
- 配列とオブジェクトの分解
- オブジェクトにおける
super
の使用 - テンプレートリテラルとデリミタ
- for...of vs for...in
- Map と WeakMap
- Set と WeakSet
- ES6のクラス
- Symbol
- Iterators
- Generators
- Promises
- 中国語 (Thanks to barretlee)
- ポルトガル語 (Thanks to alexmoreno)
- ロシア語 (Thanks to etnolover)
- 韓国語 (Thanks to scarfunk)
- フランス語 (Thanks to tnga)
- スペイン語 (Thanks to carletex)
- 日本語 (Thanks to isdh)
宣言文 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]
}
頭の片隅に覚えていてほしいこと:
let
、const
のホイスティングは今までの変数、関数のホイスティングから様変わりしました。let
もconst
もどちらも、巻き上げるが、その宣言の前にアクセスすることは出来ません。これは、Temporal Dead Zone(英語)のためです。let
とconst
のスコープは最も近い閉じたブロックになります。const
を使用する際は、大文字で記述して下さい(一般的な慣習でもあります)const
は宣言すると同時に定義しなければならなりません。
アロー関数は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
についてよく知りたければ参照
ES6では関数定義時にでデフォルトパラメーターを設定することができます。実例をみてください。
let getFinalPrice = (price, tax = 0.7) => price + price * tax;
getFinalPrice(500); // 850
...
演算子はスプレッド演算子、またはレスト演算子をとして動きます。使用の仕方によって動きが異なります。
イテラブルな何かと使用すると、...
はスプレッド演算子として働く。
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]
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()
// }
ES6では新たに8進数と2進数リテラルをサポートしました。
0o
か0O
で始まる number は8進数に変換されます。
以下のコードを見てください。
let oValue = 0o10;
console.log(oValue); // 8
let bValue = 0b10; // 0b or 0B for binary
console.log(bValue); // 2
分解によってオブジェクトと配列を扱う際に一時的な変数の使用を避けることができます。
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
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
ES6では文字列への代入をかんたんにできます。代入したと同時に自動的に評価されます。
`${ ... }`
は変数をレンダリングできる`
\
バックスラッシュはデリミタとして使用する
let user = 'Kevin';
console.log(`Hi ${user}!`); // Hi Kevin!
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
ES6には新しいデータ構造のMap
とWeakMap
があります。全てのオブジェクトは、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
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を取り除いた
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
キーワードは使用する必要はありません。
シンボル(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)
を使用して下さい。
イテレータはコレクションの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]()
を定義することで、自前のイテレータを記述することもできます。
ジェネレータ関数は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された値を計算し、効率よく、連続したシークエンスに要求に応じてアクセスできます。無限に続くシークエンスですら可能です。
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