この章では、本書のすべての章の概要セクションをまとめています。
Math機能NumberプロパティMathメソッドletconst...)Objectの新しいメソッドfor-ofループthen()呼び出しのチェーンES6仕様の導入部には、すべての新機能がリストされています。
[ECMAScript 6]の主な拡張機能には、モジュール、クラス宣言、レキシカルブロックスコープ、イテレータとジェネレーター、非同期プログラミングのためのPromise、分割代入パターン、適切な末尾呼び出しが含まれます。組み込みのECMAScriptライブラリは、マップ、セット、およびバイナリ数値の配列を含む追加のデータ抽象化をサポートするために拡張されており、文字列と正規表現のUnicode補助文字の追加サポートも提供されています。組み込みは、サブクラス化を介して拡張可能になりました。
機能には3つの主要なカテゴリがあります。
Math機能 バイナリ表記と8進数表記で整数を指定できるようになりました。
> 0xFF // ES5: hexadecimal
255
> 0b11 // ES6: binary
3
> 0o10 // ES6: octal
8
Numberプロパティ グローバルオブジェクトNumberにいくつかの新しいプロパティが追加されました。
Number.EPSILONは、丸め誤差に対する許容範囲で浮動小数点数を比較します。Number.isInteger(num)は、numが整数(小数部を持たない数値)であるかどうかをチェックします。 > Number.isInteger(1.05)
false
> Number.isInteger(1)
true
> Number.isInteger(-3.1)
false
> Number.isInteger(-3)
true
Number.isSafeInteger(number)Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGERNumber.isNaN(num)は、numが値NaNであるかどうかをチェックします。グローバル関数isNaN()とは対照的に、引数を数値に強制変換しないため、数値以外の場合でも安全です。 > isNaN('???')
true
> Number.isNaN('???')
false
Numberの3つの追加メソッドは、同じ名前のグローバル関数とほぼ同等です:Number.isFinite、Number.parseFloat、Number.parseInt。Mathメソッド グローバルオブジェクトMathには、数値演算、三角関数、ビット単位演算のための新しいメソッドがあります。4つの例を見てみましょう。
Math.sign()は数値の符号を返します。
> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1
Math.trunc()は数値の小数部を削除します。
> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3
Math.log10()は、底が10の対数を計算します。
> Math.log10(100)
2
Math.hypot()は、引数の2乗の合計の平方根(ピタゴラスの定理)を計算します。
> Math.hypot(3, 4)
5
新しい文字列メソッド
> 'hello'.startsWith('hell')
true
> 'hello'.endsWith('ello')
true
> 'hello'.includes('ell')
true
> 'doo '.repeat(3)
'doo doo doo '
ES6には、新しい種類の文字列リテラルであるテンプレートリテラルがあります。
// String interpolation via template literals (in backticks)
const first = 'Jane';
const last = 'Doe';
console.log(`Hello ${first} ${last}!`);
// Hello Jane Doe!
// Template literals also let you create strings with multiple lines
const multiLine = `
This is
a string
with multiple
lines`;
シンボルは、ECMAScript 6の新しいプリミティブ型です。ファクトリ関数を介して作成されます。
const mySymbol = Symbol('mySymbol');
ファクトリ関数を呼び出すたびに、新しく一意のシンボルが作成されます。オプションのパラメータは、シンボルを印刷するときに表示される説明的な文字列です(他の目的はありません)。
> mySymbol
Symbol(mySymbol)
シンボルは主に一意のプロパティキーとして使用されます。シンボルは、他のプロパティキー(シンボルまたは文字列)と衝突することはありません。たとえば、Symbol.iteratorに格納されているシンボルをメソッドのキーとして使用することにより、オブジェクトをイテラブル(for-ofループおよびその他の言語メカニズムを介して使用可能)にすることができます(イテラブルの詳細については、イテレーションの章で説明します)。
const iterableObject = {
[Symbol.iterator]() { // (A)
···
}
}
for (const x of iterableObject) {
console.log(x);
}
// Output:
// hello
// world
A行では、シンボルがメソッドのキーとして使用されています。この一意のマーカーにより、オブジェクトはイテラブルになり、for-ofループを使用できるようになります。
ECMAScript 5では、色などの概念を表すために文字列を使用していた可能性があります。ES6では、シンボルを使用すると、常に一意であることが保証されます。
const COLOR_RED = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN = Symbol('Green');
const COLOR_BLUE = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_ORANGE:
return COLOR_BLUE;
case COLOR_YELLOW:
return COLOR_VIOLET;
case COLOR_GREEN:
return COLOR_RED;
case COLOR_BLUE:
return COLOR_ORANGE;
case COLOR_VIOLET:
return COLOR_YELLOW;
default:
throw new Exception('Unknown color: '+color);
}
}
Symbol('Red')を呼び出すたびに、新しいシンボルが作成されます。したがって、COLOR_REDを別の値と間違えることはありません。それが文字列'Red'であった場合とは異なります。
シンボルを文字列に強制変換(暗黙的に変換)すると、例外がスローされます。
const sym = Symbol('desc');
const str1 = '' + sym; // TypeError
const str2 = `${sym}`; // TypeError
唯一の解決策は、明示的に変換することです。
const str2 = String(sym); // 'Symbol(desc)'
const str3 = sym.toString(); // 'Symbol(desc)'
強制変換を禁止すると、一部のエラーが防止されますが、シンボルの操作も複雑になります。
次の操作は、プロパティキーとしてシンボルを認識します。
Reflect.ownKeys()[]を介したプロパティアクセスObject.assign()次の操作は、プロパティキーとしてシンボルを無視します。
Object.keys()Object.getOwnPropertyNames()for-inループES6には、テンプレートリテラルとタグ付きテンプレートリテラルの2つの新しい種類のリテラルがあります。これらの2つのリテラルは似たような名前で見た目も似ていますが、まったく異なります。したがって、区別することが重要です。
テンプレートリテラルは、複数行にまたがり、補間式(${···}を介して挿入)を含むことができる文字列リテラルです。
const firstName = 'Jane';
console.log(`Hello ${firstName}!
How are you
today?`);
// Output:
// Hello Jane!
// How are you
// today?
タグ付きテンプレートリテラル(略してタグ付きテンプレート)は、テンプレートリテラルの前に関数を記述することによって作成されます。
> String.raw`A \tagged\ template`
'A \\tagged\\ template'
タグ付きテンプレートは関数呼び出しです。前の例では、タグ付きテンプレートの結果を生成するために、メソッドString.rawが呼び出されます。
ES6では、変数を宣言する2つの新しい方法であるletとconstが提供されます。これらは主に、ES5での変数の宣言方法であるvarに代わるものです。
let let は var と同様に動作しますが、宣言された変数がブロックスコープを持つ点が異なります。つまり、現在のブロック内でのみ存在します。一方、var は関数スコープです。
次のコードでは、let で宣言された変数 tmp が、A行から始まるブロック内でのみ存在することがわかります。
function order(x, y) {
if (x > y) { // (A)
let tmp = x;
x = y;
y = tmp;
}
console.log(tmp===x); // ReferenceError: tmp is not defined
return [x, y];
}
const const は let と同様に動作しますが、宣言する変数はすぐに初期化する必要があり、その後値を変更することはできません。
const foo;
// SyntaxError: missing = in const declaration
const bar = 123;
bar = 456;
// TypeError: `bar` is read-only
for-of はループの反復ごとに1つのバインディング(変数のためのストレージスペース)を作成するため、ループ変数を const で宣言しても問題ありません。
for (const x of ['a', 'b']) {
console.log(x);
}
// Output:
// a
// b
次の表は、ES6で変数を宣言できる6つの方法の概要を示しています(kangax氏による表に触発されました)。
| ホイスティング | スコープ | グローバルプロパティを作成するか | |
|---|---|---|---|
var |
宣言 | 関数 | はい |
let |
一時的なデッドゾーン | ブロック | いいえ |
const |
一時的なデッドゾーン | ブロック | いいえ |
関数 |
完全 | ブロック | はい |
class |
いいえ | ブロック | いいえ |
import |
完全 | モジュールグローバル | いいえ |
分割代入 は、(ネストされている可能性のある)オブジェクトや配列に格納されたデータから複数の値を抽出する便利な方法です。データの受け渡し場所(代入の左辺など)で使用できます。値の抽出方法はパターンによって指定されます(例については以下を参照)。
オブジェクトの分割代入
const obj = { first: 'Jane', last: 'Doe' };
const {first: f, last: l} = obj;
// f = 'Jane'; l = 'Doe'
// {prop} is short for {prop: prop}
const {first, last} = obj;
// first = 'Jane'; last = 'Doe'
分割代入は戻り値の処理に役立ちます
const obj = { foo: 123 };
const {writable, configurable} =
Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(writable, configurable); // true true
配列の分割代入(すべての反復可能な値に対して機能します)
const iterable = ['a', 'b'];
const [x, y] = iterable;
// x = 'a'; y = 'b'
分割代入は戻り値の処理に役立ちます
const [all, year, month, day] =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
分割代入は以下の場所で使用できます(デモンストレーションのために配列パターンを示しています。オブジェクトパターンも同様に機能します)。
// Variable declarations:
const [x] = ['a'];
let [x] = ['a'];
var [x] = ['a'];
// Assignments:
[x] = ['a'];
// Parameter definitions:
function f([x]) { ··· }
f(['a']);
for-of ループ内でも分割代入を使用できます
const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
console.log(index, element);
}
// Output:
// 0 a
// 1 b
ECMAScript 6では、パラメータ処理が大幅にアップグレードされました。パラメータのデフォルト値、レストパラメータ(可変長引数)、分割代入がサポートされるようになりました。
さらに、スプレッド演算子は、関数/メソッド/コンストラクタ呼び出しと配列リテラルで役立ちます。
デフォルトパラメータ値は、等号(=)を介してパラメータに指定されます。呼び出し元がパラメータの値を提供しない場合、デフォルト値が使用されます。次の例では、y のデフォルトパラメータ値は0です。
function func(x, y=0) {
return [x, y];
}
func(1, 2); // [1, 2]
func(1); // [1, 0]
func(); // [undefined, 0]
パラメータ名の前にレスト演算子(...)を付けると、そのパラメータは配列を介して残りのすべてのパラメータを受け取ります。
function format(pattern, ...params) {
return {pattern, params};
}
format(1, 2, 3);
// { pattern: 1, params: [ 2, 3 ] }
format();
// { pattern: undefined, params: [] }
パラメータリストでオブジェクトパターンを使用して分割代入すると、名前付きパラメータをシミュレートできます。
function selectEntries({ start=0, end=-1, step=1 } = {}) { // (A)
// The object pattern is an abbreviation of:
// { start: start=0, end: end=-1, step: step=1 }
// Use the variables `start`, `end` and `step` here
···
}
selectEntries({ start: 10, end: 30, step: 2 });
selectEntries({ step: 3 });
selectEntries({});
selectEntries();
A行の = {} を使用すると、パラメータなしで selectEntries() を呼び出すことができます。
...) 関数とコンストラクタの呼び出しでは、スプレッド演算子は反復可能な値を引数に変換します。
> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
> Math.max(-1, ...[-5, 11], 3)
11
配列リテラルでは、スプレッド演算子は反復可能な値を配列要素に変換します。
> [1, ...[2,3], 4]
[1, 2, 3, 4]
ES5では、単一の構成要素である(従来の)関数が3つの役割を果たしていました。
ES6では、より特化が進んでいます。3つの役割は、現在次のように処理されています。関数の定義とクラスの定義に関する限り、定義は宣言または式になります。
特にコールバックの場合、アロー関数は周囲のスコープの this を隠蔽しないため便利です。
長いコールバックやスタンドアロン関数の場合、従来の関数でも問題ありません。一部のAPIでは、暗黙のパラメータとして this を使用します。その場合、従来の関数を使用するしかありません。
私は以下を区別することに注意してください。
(後で説明するように)動作が異なるにもかかわらず、これらのエンティティはすべて関数です。例えば
> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'
アロー関数には2つの利点があります。
1つ目は、従来の関数式よりも冗長でないことです。
const arr = [1, 2, 3];
const squares = arr.map(x => x * x);
// Traditional function expression:
const squares = arr.map(function (x) { return x * x });
2つ目は、その this が周囲(レキシカル)から取得されることです。したがって、bind() や that = this はもう必要ありません。
function UiComponent() {
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('CLICK');
this.handleClick(); // lexical `this`
});
}
次の変数はすべてアロー関数内でレキシカルです。
argumentssuperthisnew.targetメソッド定義
const obj = {
myMethod(x, y) {
···
}
};
プロパティ値の短縮形
const first = 'Jane';
const last = 'Doe';
const obj = { first, last };
// Same as:
const obj = { first: first, last: last };
算出されたプロパティキー
const propKey = 'foo';
const obj = {
[propKey]: true,
['b'+'ar']: 123
};
この新しい構文は、メソッド定義にも使用できます
const obj = {
['h'+'ello']() {
return 'hi';
}
};
console.log(obj.hello()); // hi
算出されたプロパティキーの主な使用例は、シンボルをプロパティキーとして簡単に使用できるようにすることです。
Object の新しいメソッド Object の最も重要な新しいメソッドは assign() です。従来、この機能はJavaScriptの世界では extend() と呼ばれていました。この従来の操作とは対照的に、Object.assign() は独自の(継承されていない)プロパティのみを考慮します。
const obj = { foo: 123 };
Object.assign(obj, { bar: true });
console.log(JSON.stringify(obj));
// {"foo":123,"bar":true}
クラスとサブクラス
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
toString() {
return super.toString() + ' in ' + this.color;
}
}
クラスの使用
> const cp = new ColorPoint(25, 8, 'green');
> cp.toString();
'(25, 8) in green'
> cp instanceof ColorPoint
true
> cp instanceof Point
true
内部的には、ES6クラスは根本的に新しいものではありません。主に、古いスタイルのコンストラクタ関数を作成するためのより便利な構文を提供します。typeof を使用すると、それが確認できます。
> typeof Point
'function'
JavaScriptには長い間モジュールがありましたが、言語に組み込まれたものではなく、ライブラリを介して実装されていました。ES6は、JavaScriptに組み込みモジュールが搭載された初めてのバージョンです。
ES6モジュールはファイルに格納されます。ファイルごとに1つのモジュールがあり、モジュールごとに1つのファイルがあります。モジュールからものをエクスポートする方法は2つあります。これら2つの方法は混合できますが、通常は別々に使用することをお勧めします。
複数の名前付きエクスポートが可能です。
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
モジュール全体をインポートすることもできます。
//------ main.js ------
import * as lib from 'lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
単一のデフォルトエクスポートが可能です。例えば、関数
//------ myFunc.js ------
export default function () { ··· } // no semicolon!
//------ main1.js ------
import myFunc from 'myFunc';
myFunc();
またはクラス
//------ MyClass.js ------
export default class { ··· } // no semicolon!
//------ main2.js ------
import MyClass from 'MyClass';
const inst = new MyClass();
関数またはクラス(匿名宣言)をデフォルトでエクスポートする場合、末尾にセミコロンがないことに注意してください。
| スクリプト | モジュール | |
|---|---|---|
| HTML要素 | <script> |
<script type="module"> |
| デフォルトモード | 非strict | strict |
| トップレベルの変数は | グローバル | モジュールに対してローカル |
トップレベルでのthisの値 |
window |
undefined |
| 実行 | 同期的に | 非同期的に |
宣言的なインポート(importステートメント) |
いいえ | はい |
| プログラム的なインポート(PromiseベースのAPI) | はい | はい |
| ファイル拡張子 | .js |
.js |
for-of ループ for-of は、ES6の新しいループで、for-in と forEach() の両方を置き換え、新しいイテレーションプロトコルをサポートします。
反復可能なオブジェクト(配列、文字列、マップ、セットなど。チャプター「反復可能オブジェクトとイテレーター」を参照)をループ処理するために使用します
const iterable = ['a', 'b'];
for (const x of iterable) {
console.log(x);
}
// Output:
// a
// b
break と continue は for-of ループ内で機能します。
for (const x of ['a', '', 'b']) {
if (x.length === 0) break;
console.log(x);
}
// Output:
// a
配列をループ処理中に、要素とそのインデックスの両方にアクセスします(of の前の角かっこは、分割代入を使用していることを意味します)。
const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
console.log(`${index}. ${element}`);
}
// Output:
// 0. a
// 1. b
マップ内の [キー、値] エントリをループ処理します(of の前の角かっこは、分割代入を使用していることを意味します)。
const map = new Map([
[false, 'no'],
[true, 'yes'],
]);
for (const [key, value] of map) {
console.log(`${key} => ${value}`);
}
// Output:
// false => no
// true => yes
新しい静的 Array メソッド
Array.from(arrayLike, mapFunc?, thisArg?)Array.of(...items)新しい Array.prototype メソッド
Array.prototype.entries()Array.prototype.keys()Array.prototype.values()Array.prototype.find(predicate, thisArg?)Array.prototype.findIndex(predicate, thisArg?)Array.prototype.copyWithin(target, start, end=this.length)Array.prototype.fill(value, start=0, end=this.length)特に、ECMAScript 6では、Map、WeakMap、Set、WeakSetの4つのデータ構造が新しく追加されました。
マップのキーには任意の値を指定できます
> const map = new Map(); // create an empty Map
> const KEY = {};
> map.set(KEY, 123);
> map.get(KEY)
123
> map.has(KEY)
true
> map.delete(KEY);
true
> map.has(KEY)
false
マップの初期データを設定するために、[キー、値]ペアの配列(または任意の反復可能オブジェクト)を使用できます
const map = new Map([
[ 1, 'one' ],
[ 2, 'two' ],
[ 3, 'three' ], // trailing comma is ignored
]);
セットは一意の要素のコレクションです
const arr = [5, 1, 5, 7, 7, 5];
const unique = [...new Set(arr)]; // [ 5, 1, 7 ]
ご覧のとおり、コンストラクタにこれらの要素に対する反復可能オブジェクト(例の arr)を渡すと、要素を使用してセットを初期化できます。
WeakMapは、キーがガベージコレクションされるのを妨げないマップです。つまり、メモリリークを心配することなく、オブジェクトにデータを関連付けることができます。例:
//----- Manage listeners
const _objToListeners = new WeakMap();
function addListener(obj, listener) {
if (! _objToListeners.has(obj)) {
_objToListeners.set(obj, new Set());
}
_objToListeners.get(obj).add(listener);
}
function triggerListeners(obj) {
const listeners = _objToListeners.get(obj);
if (listeners) {
for (const listener of listeners) {
listener();
}
}
}
//----- Example: attach listeners to an object
const obj = {};
addListener(obj, () => console.log('hello'));
addListener(obj, () => console.log('world'));
//----- Example: trigger listeners
triggerListeners(obj);
// Output:
// hello
// world
型付き配列は、バイナリデータを処理するためのECMAScript 6 APIです。
コード例
const typedArray = new Uint8Array([0,1,2]);
console.log(typedArray.length); // 3
typedArray[0] = 5;
const normalArray = [...typedArray]; // [5,1,2]
// The elements are stored in typedArray.buffer.
// Get a different view on the same data:
const dataView = new DataView(typedArray.buffer);
console.log(dataView.getUint8(0)); // 5
ArrayBuffer のインスタンスは、処理されるバイナリデータを格納します。データをアクセスするために2種類のビューが使用されます。
Uint8Array、Int16Array、Float32Arrayなど)は、ArrayBufferを単一型の要素のインデックス付きシーケンスとして解釈します。DataView のインスタンスを使用すると、ArrayBuffer内の任意のバイトオフセットで、複数の型(Uint8、Int16、Float32など)の要素としてデータにアクセスできます。次のブラウザAPIは型付き配列をサポートしています(詳細については、専用のセクションで説明します)。
ES6 では、データを走査するための新しいメカニズムとしてイテレーションが導入されました。イテレーションには、2 つの中心的な概念があります。
Symbol.iterator であるメソッドを実装します。そのメソッドは、イテレーターのファクトリーとなります。TypeScript の表記法でインターフェースとして表現すると、これらの役割は次のようになります。
interface Iterable {
[Symbol.iterator]() : Iterator;
}
interface Iterator {
next() : IteratorResult;
}
interface IteratorResult {
value: any;
done: boolean;
}
以下の値はイテラブルです。
プレーンオブジェクトはイテラブルではありません(その理由は専用のセクションで説明します)。
イテレーションを通してデータにアクセスする言語構文:
const [a,b] = new Set(['a', 'b', 'c']);
for-of ループ for (const x of ['a', 'b', 'c']) {
console.log(x);
}
Array.from():
const arr = Array.from(new Set(['a', 'b', 'c']));
...) const arr = [...new Set(['a', 'b', 'c'])];
const map = new Map([[false, 'no'], [true, 'yes']]);
const set = new Set(['a', 'b', 'c']);
Promise.all()、Promise.race() Promise.all(iterableOverPromises).then(···);
Promise.race(iterableOverPromises).then(···);
yield*:
yield* anIterable;
ジェネレーターは、一時停止と再開が可能なプロセス(コードの一部)と考えることができます。
function* genFunc() {
// (A)
console.log('First');
yield;
console.log('Second');
}
新しい構文に注目してください:function* は、ジェネレーター関数のための新しい「キーワード」です(ジェネレーターメソッドもあります)。yield は、ジェネレーターが自身を一時停止させることができる演算子です。さらに、ジェネレーターは yield を介して入力を受け取り、出力を送信することもできます。
ジェネレーター関数 genFunc() を呼び出すと、プロセスを制御するために使用できるジェネレーターオブジェクト genObj が得られます。
const genObj = genFunc();
プロセスは、最初は A 行で一時停止しています。genObj.next() は実行を再開し、genFunc() 内の yield が実行を一時停止します。
genObj.next();
// Output: First
genObj.next();
// output: Second
ジェネレーターには 4 つの種類があります。
function* genFunc() { ··· }
const genObj = genFunc();
const genFunc = function* () { ··· };
const genObj = genFunc();
const obj = {
* generatorMethod() {
···
}
};
const genObj = obj.generatorMethod();
class MyClass {
* generatorMethod() {
···
}
}
const myInst = new MyClass();
const genObj = myInst.generatorMethod();
ジェネレーターによって返されるオブジェクトはイテラブルです。各 yield は、イテレートされた値のシーケンスに寄与します。したがって、ジェネレーターを使用してイテラブルを実装し、それをさまざまな ES6 言語メカニズム (for-of ループ、スプレッド演算子 (...) など) で利用できます。
次の関数は、オブジェクトのプロパティを反復処理し、各プロパティごとに [キー、値] のペアを返します。
function* objectEntries(obj) {
const propKeys = Reflect.ownKeys(obj);
for (const propKey of propKeys) {
// `yield` returns a value and then pauses
// the generator. Later, execution continues
// where it was previously paused.
yield [propKey, obj[propKey]];
}
}
objectEntries() はこのように使用します。
const jane = { first: 'Jane', last: 'Doe' };
for (const [key,value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// Output:
// first: Jane
// last: Doe
objectEntries() が正確にどのように機能するかは、専用のセクションで説明します。ジェネレーターを使用せずに同じ機能を実装するには、多くの作業が必要です。
ジェネレーターを使用すると、Promise の操作を大幅に簡略化できます。Promise ベースの関数 fetchJson() と、ジェネレーターを使用してどのように改善できるかを見てみましょう。
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
ライブラリ co とジェネレーターを使用すると、この非同期コードは同期的に見えます。
const fetchJson = co.wrap(function* (url) {
try {
let request = yield fetch(url);
let text = yield request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
});
ECMAScript 2017 では、内部的にジェネレーターに基づいた async 関数が導入されます。async 関数を使用すると、コードは次のようになります。
async function fetchJson(url) {
try {
let request = await fetch(url);
let text = await request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
}
すべてのバージョンは次のように呼び出すことができます。
fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));
ジェネレーターは、yield を介して next() から入力を受け取ることができます。つまり、新しいデータが非同期的に到着するたびにジェネレーターを起動することができ、ジェネレーターにとっては同期的にデータを受け取っているように感じられます。
以下の正規表現機能は、ECMAScript 6 で新たに追加されたものです。
/y(sticky)は、正規表現の各一致を、前のマッチの末尾に固定します。/u (unicode) は、サロゲートペア (\uD83D\uDE80 など) をコードポイントとして処理し、正規表現で Unicode コードポイントエスケープ (\u{1F680} など) を使用できるようにします。flags は、source がすでに ES5 でパターンにアクセスできるように、正規表現のフラグへのアクセスを提供します。 > /abc/ig.source // ES5
'abc'
> /abc/ig.flags // ES6
'gi'
RegExp() を使用して、正規表現のコピーを作成できます。 > new RegExp(/abc/ig).flags
'gi'
> new RegExp(/abc/ig, 'i').flags // change flags
'i'
Promise は、非同期計算の結果を配信するためのコールバックの代替手段です。非同期関数の実装者にはより多くの労力がかかりますが、それらの関数のユーザーにはいくつかの利点があります。
次の関数は、Promise を介して非同期的に結果を返します。
function asyncFunc() {
return new Promise(
function (resolve, reject) {
···
resolve(result);
···
reject(error);
});
}
asyncFunc() は次のように呼び出します。
asyncFunc()
.then(result => { ··· })
.catch(error => { ··· });
then() 呼び出しの連鎖 then() は常に Promise を返すため、メソッド呼び出しを連鎖させることができます。
asyncFunc1()
.then(result1 => {
// Use result1
return asyncFunction2(); // (A)
})
.then(result2 => { // (B)
// Use result2
})
.catch(error => {
// Handle errors of asyncFunc1() and asyncFunc2()
});
then() によって返される Promise P がどのように解決されるかは、そのコールバックが何を行うかによって決まります。
asyncFunction2 の Promise の解決を拾うことができます。さらに、catch() が 2 つの非同期関数呼び出し (asyncFunction1() と asyncFunction2()) のエラーをどのように処理するかに注意してください。つまり、キャッチされないエラーは、エラーハンドラーが存在するまで渡されます。
then() を介して非同期関数呼び出しを連鎖させると、それらは順番に 1 つずつ実行されます。
asyncFunc1()
.then(() => asyncFunc2());
そうせずに、すべてをすぐに呼び出すと、それらは基本的に並行して実行されます (Unix プロセスの用語で言うと *fork* )。
asyncFunc1();
asyncFunc2();
Promise.all() を使用すると、すべての結果が揃ったときに通知を受け取ることができます (Unix プロセスの用語で言うと *join* )。その入力は Promise の配列で、その出力は、結果の配列で満たされた単一の Promise です。
Promise.all([
asyncFunc1(),
asyncFunc2(),
])
.then(([result1, result2]) => {
···
})
.catch(err => {
// Receives first rejection among the Promises
···
});
Promise API は、結果を非同期的に配信するためのものです。Promise オブジェクト (略して Promise) は、そのオブジェクトを介して配信される結果の代わりとなるものです。
状態
状態の変化への反応
then() で登録するコールバックです。then() メソッドを持つオブジェクトです。API が解決の通知だけに関心がある場合は常に、thenable のみを要求します (例:then() および catch() から返される値。または Promise.all() および Promise.race() に渡される値)。状態の変更:Promise の状態を変更するための 2 つの操作があります。どちらかを一度呼び出すと、それ以降の呼び出しは効果がありません。
プロキシを使用すると、オブジェクトに対して実行される操作 (プロパティの取得など) をインターセプトしてカスタマイズできます。これらは、メタプログラミング機能です。
次の例では、proxy は操作をインターセプトしているオブジェクトであり、handler はインターセプトを処理するオブジェクトです。この場合、単一の操作 get (プロパティの取得) のみをインターセプトしています。
const target = {};
const handler = {
get(target, propKey, receiver) {
console.log('get ' + propKey);
return 123;
}
};
const proxy = new Proxy(target, handler);
プロパティ proxy.foo を取得すると、ハンドラーはその操作をインターセプトします。
> proxy.foo
get foo
123
インターセプトできる操作のリストについては、完全な API のリファレンスを参照してください。