メインコンテンツへ移動
黯羽軽揚毎日少しずつ

ES2019

無料2026-03-05#JS#ES10#ES 2019#ES2019指南#ES2019总结#ES2019教程

一.概観

2019 年 6 月に ES2019 仕様、つまり ES10 がリリースされました

4 つの新特性が含まれます:

および 6 つの構文/意味論上の変化:

  • Optional catch binding:try-catch 構造中の catch ブロックのパラメータ部分を省略可能

  • Array.prototype.sort:ソートアルゴリズムは安定である必要がある(等しい要素のソート前後の順序は不変)

  • Well-formed JSON.stringify:JSON.stringify がフォーマットの良い UTF-8 文字列を返すことを要求

  • JSON superset:文字列リテラル中に U+2028(LINE SEPARATOR) と U+2029(PARAGRAPH SEPARATOR) の出現を許可

  • Function.prototype.toString revision:function ソーステキスト、または標準プレースホルダーを返すことを要求

P.S.V8 v7.3+、Chrome 73+ は ES2019 のすべての特性をサポート

二.Array.prototype.{flat,flatMap}

flat

Array.prototype.flat( [ depth ] )

配列を平坦化する flatten メソッドで、オプションの depth パラメータをサポートし、指定された層数を平坦化することを表します(デフォルトは 1):

[[1], [[2]], [[[3]]]].flat()
// [1, [2], [[3]]] を取得
[[1], [[2]], [[[3]]]].flat(Infinity)
// [1, 2, 3] を取得

簡単な実装は以下の通り:

const flat = (arr, depth = 1) => {
  if (depth > 0) {
    const flated = Array.prototype.concat.apply([], arr);
    // または
    // const flated = arr.reduce((a, v) => a.concat(v), []);
    const isFullFlated = flated.reduce((a, v) => a && !Array.isArray(v), true);
    return isFullFlated ? flated : flat(flated, depth - 1);
  }

  return arr;
};

flatMap

Array.prototype.flatMap ( mapperFunction [ , thisArg ] )

P.S. オプションパラメータ thisArg は mapperFunction 中の this として使用されます。例えば:

[1, 2, 3, 4].flatMap(function(x) {
  return this.value ** x;
}, { value: 2 })
// [2, 4, 8, 16] を取得

作用上、flatMap は map と類似しており、主な違いは:map は 1 対 1 のマッピングを行い、flatMap は 1 対多(0 個に対応することも可能)をサポートします

例えば:

[2, 0, 1, 9].flatMap(x => new Array(x).fill(x))
// [2, 2, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9] を取得

各要素を配列にマッピングし、最後に 1 層を平坦化するのに相当します:

// 性能を考慮しない場合、このように簡単に実装可能
// const flatMap = (arr, f) => arr.map(f).flat();
// または
// const flatMap = (arr, f) => arr.reduce((a, v) => a.concat(f(v)), []);

主に 2 つの应用场景があります:

  • map + filter:空配列を返すのは 1 対 0 を表し、つまり filter

  • 1 対多マッピング

例えば指定ディレクトリ内のすべての非隠しファイルをリスト:

// node 12.10.0
const fs = require('fs');
const path = require('path');

// map + filter 結合 1 対多マッピング
const listFiles = dir => fs.readdirSync(dir).flatMap(f => {
  if (f.startsWith('.')) return [];

  const filePath = path.join(dir, f);
  return fs.statSync(filePath).isDirectory() ? listFiles(filePath) : filePath;
});

三.Object.fromEntries

Object.fromEntries ( iterable )

一組の鍵値対をオブジェクトに変換するために使用され、Object.entries 逆演算に相当し、データタイプ変換上の欠落を補足 するために使用されます(key-value pairs to Object):

const entries = Object.entries({ a: 1, b: 2 });
// [["a", 1], ["b", 2]] を取得
const obj = Object.fromEntries(entries);
// {a: 1, b: 2} を取得

lodash が提供する _.fromPairs(pairs) に類似しており、簡単な実装は以下の通り:

const fromEntries = pairs => pairs.reduce((acc, [ key, val ]) => Object.assign(acc, { [key]: val }), {});

P.S. 公式 polyfill は es-shims/Object.fromEntries を参照

特殊的:

  • key が同じ鍵値対が存在する場合、後が前をカバー

  • Symbol を key として使用可能(Object.entries は Symbol key を無視)

  • 鍵値対中の非 String/Symbol タイプの key は強制的に String に変換

  • パラメータは iterable をサポートし、配列に限定されない

  • 列挙可能なデータ属性の作成のみをサポート

例えば:

// 1.key が同じ鍵値対が存在する場合、後が前をカバー
Object.fromEntries([['a', 1], ['b', 2], ['a', 3]]);
// {a: 3, b: 2} を取得

// 2.Symbol を key として使用可能(`Object.entries` は Symbol key を無視)
Object.fromEntries([[Symbol('a'), 1], ['b', 2]]);
// {b: 2, Symbol(a): 1} を取得

// 3.鍵値対中の非 String/Symbol タイプの key は強制的に String に変換
Object.fromEntries([[new Error('here'), 1], [{}, 2]]);
// {['Error: here']: 1, ['[object Object]']: 2} を取得

// 4.パラメータは iterable をサポートし、配列に限定されない
Object.fromEntries(function*(){
  yield ['a', 1];
  yield ['b', 2];
}());
// {a: 1, b: 2} を取得

// 5.列挙可能なデータ属性の作成のみをサポート
Object.getOwnPropertyDescriptors(Object.fromEntries([['a', 1]]))
// { a: {value: 1, writable: true, enumerable: true, configurable: true} } を取得

四.String.prototype.{trimStart,trimEnd}

trimLeft/trimRight の標準定義と算是し、命名上は ES2017 の padStart/padEnd と一致を保つためです

機能上、空白文字及び改行符は trim されます:

// 空白文字 https://tc39.github.io/ecma262/#sec-white-space
'\u0009'  // <TAB> CHARACTER TABULATION
'\u000B'  // <VT> LINE TABULATION
'\u000C'  // <FF> FORM FEED
'\u0020'  // <SP> SPACE
'\u00A0'  // <NBSP> NO-BREAK SPACE
'\uFEFF'  // <ZWNBSP> (ZERO WIDTH NO-BREAK SPACE
// ...および其它 Space_Separator 類下に White_Space 属性を持つ Unicode 文字

// 改行符 https://tc39.github.io/ecma262/#sec-line-terminators
'\u000A'  // <LF> LINE FEED
'\u000D'  // <CR> CARRIAGE RETURN
'\u2028'  // <LS> LINE SEPARATOR
'\u2029'  // <PS> PARAGRAPH SEPARATOR

例えば:

'\u0009\u000B\u000C\u0020\u00A0\uFEFF\u000A\u000D\u2028\u2029'.trim().length === 0
'\u0009\u000B\u000C\u0020\u00A0\uFEFF\u000A\u000D\u2028\u2029'.trimStart().length === 0
'\u0009\u000B\u000C\u0020\u00A0\uFEFF\u000A\u000D\u2028\u2029'.trimEnd().length === 0

さらに、後方互換のため、trimLeft/trimRight は依然として保留され、仕様 Annex B(B Additional ECMAScript Features for Web Browsers、Web ブラウザ実装を要求)中に定義されていますが、trimStart/trimEnd の使用を推奨:

The property trimStart is preferred. The trimLeft property is provided principally for compatibility with old code. It is recommended that the trimStart property be used in new ECMAScript code.

二者は別名関係なので、面白いことが起こりました:

String.prototype.trimLeft.name === 'trimStart'
String.prototype.trimRight.name === 'trimEnd'

五.Symbol.prototype.description

[Symbol 作成時に传入された description パラメータ](/articles/symbol-es6 ノート 7/#articleHeader3) に Symbol.prototype.description を通じてアクセスすることを許可します。例えば:

const mySymbol = Symbol('my description for this Symbol');
mySymbol.description === 'my description for this Symbol'

以前は toString で該説明情報を截取するしかありませんでした:

mySymbol.toString().match(/Symbol\(([^)]*)\)$/)[1]

P.S.description 属性は読み取り専用です:

Symbol.prototype.description is an accessor property whose set accessor function is undefined.

六.構文/意味論変化

Optional catch binding

予想内の異常に対して、通常このようにします:

try {
  JSON.parse('');
} catch(err) { /* noop */ }

err パラメータを使用していませんが、宣言する必要があります。パラメータを省略すると、構文解析エラーが存在します:

try {
  JSON.parse('');
} catch() { }
// エラー Uncaught SyntaxError: Unexpected token )

ES2019 は try-catch 構造中の catch ブロックのパラメータ部分を省略することを許可:

Allow developers to use try/catch without creating an unused binding

構文上、2 種類の形式の catch ブロックをサポート:

// パラメータ部分を持つ catch ブロック
catch( CatchParameter[?Yield, ?Await] ) Block[?Yield, ?Await, ?Return]
// パラメータ部分を省略した catch ブロック
catch Block[?Yield, ?Await, ?Return]

例えば:

// node 12.10.0
const parseJSON = (str = '') => {
  let json;
  try {
    json = JSON.parse(str);
  } catch {
    consle.error('parseJSON error, just ignore it.');
  }
};

parseJSON('');
// parseJSON error, just ignore it. を出力

理論上、大多数の场景中の異常情報は無視すべきではありません(記録するか、投げ出すか、善後策を講じる)。比較的合理的な数種類の场景 は:

  • assert.throws(func):テスト駆動ライブラリ用、指定関数実行が異常を投げることを断言(どのような異常かは関心なし)

  • ブラウザ特性検出:特定特性をサポートするかどうかだけを知りたい

  • 善後措置異常:例えば logError() 自身が異常を発生、捕獲できても為す術なし

P.S. これらの场景でも、ある異常を無視すると決定する時は注釈中で原因を説明すべき

Array.prototype.sort

安定ソートでなければならない(ソート前後の等しい要素の相対順序は不変):

The sort must be stable (that is, elements that compare equal must remain in their original order).

例えば:

const words = [{ id: 1, value: 'I' }, { id: 3, value: 'am' }, { id: 1, value: 'feeling' }, { id: 4, value: 'lucky' }];
words.sort((a, b) => a.id - b.id);
console.log(words.map(v => v.value).join(' '));
// 期待結果は I feeling am lucky
// feeling I am lucky ではない

Well-formed JSON.stringify

JSON 仕様 は広く通用する JSON は UTF-8 エンコードを使用すべきと要求:

JSON text exchanged between systems that are not part of a closed ecosystem MUST be encoded using UTF-8.

JavaScript 中で、単独で出現する半分の 代理対儿 に対して、JSON.stringify() 時に問題が存在:

JSON.stringify('\uD800')
// '"?"' を取得

実際には、JSON は \u 形式のエスケープ構文をサポートするので、ES2019 は JSON.stringify() がフォーマットの良い UTF-8 エンコード文字列を返すことを要求:

JSON.stringify('\uD800');
// '"\\ud800"' を取得

JSON.stringify() の bug 修復と算是

P.S. JavaScript 中の Unicode に関するより多くの情報は、JavaScript 中の Unicode を参照

JSON superset

リテラル形式の(エスケープされていない)U+2028 と U+2029 文字は JSON 中では合法ですが、JavaScript 文字列リテラル中では非法文字です:

const LS = "?";
const PS = eval("'\u2029'");
// エラー Uncaught SyntaxError: Invalid or unexpected token

ES2019 仕様は文字列リテラルが完全な JSON 文字セットをサポートすることを要求、つまりJavaScript は JSON の超集。ES2019 をサポートする環境中では、二重引用符/単一引用符中の U+2028 と U+2029 文字に対して、上記構文エラーを投げなくなりました(正規表現リテラル中では依然としてこれら 2 つの文字の出現を許可しません)

P.S.[テンプレート文字列](/articles/テンプレート文字列-es6 ノート 3/#articleHeader5) はこの問題が存在しません:

const LS = `?`;
const PS = eval("`\u2029`");

Function.prototype.toString revision

function ソーステキスト、または標準プレースホルダーを返すことを要求:

implementations must not be required to retain source text for all functions defined using ECMAScript code

具体的には以下の通り:

  • 関数が ES コードを通じて作成された場合、toString() はそのソースを返さなければならない

  • toString() が合法な ES コードを取得できない場合、標準プレースホルダーを返す。プレースホルダー文字列は必ず合法な ES コードであってはならない(eval(プレースホルダー) は必ず SyntaxError を投げる)

P.S. 仕様が推奨するプレースホルダー形式は "function" BindingIdentifier? "(" FormalParameters ")" "{ [native code] }" で、パラメータは省略可能で、かつ内置メソッドはメソッド名を给出する必要があります。例えば:

document.createAttribute.toString()
// "function createAttribute() { [native code] }" を出力

特殊的:

  • toString() が返す関数ソースは必ずしも合法ではなく、その詞法上下文中のみ合法な可能性がある

  • Function コンストラクタ等方式を通じて動的に作成された関数も、toString() が適切なソースを返すことを要求

    // 1.toString() 戻り値はその詞法上下文中のみ合法な可能性がある class C { foo() { /hello/ } } const source = C.prototype.foo.toString(); eval(source) // エラー Uncaught SyntaxError: Unexpected token {

    // 2.Function コンストラクタ等方式を通じて動的に作成された関数もサポート new Function('a', 'b', 'return a + b;').toString() // function anonymous(a,b) { return a + b; } を出力

七.まとめ

flat/flatMap、trimStart/trimEnd などのツール関数はすべて標準に組み込まれ、Object はまた一つどうでもいいメソッドが増え、Symbol はその説明情報を直接読み取れるようになりました

さらに、構文/意味論上でもいくつかの修正が行われ、catch ブロックのパラメータ部分を省略可能、配列 sort() は安定ソートでなければならない、関数 toString() の具体的実装を明確、JSON サポートを完善、JSON の超集になることを期待(JSON ? ECMAScript)

参考資料

コメント

コメントはまだありません

コメントを書く