メインコンテンツへ移動

ES2019

無料2019-09-28#JS#ES10#ES 2019#ES2019指南#ES2019总结#ES2019教程

いくつかの小さな特性が追加され、構文/意味論上でいくつかの修正が行われました。总之、今回の更新では、大きな事はありません

一.概観

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

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

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

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

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

  • Well-formed JSON.stringifyJSON.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. オプションパラメータ thisArgmapperFunction 中の this として使用されます。例えば:

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

作用上、flatMapmap と類似しており、主な違いは: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+2028U+2029 文字は JSON 中では合法ですが、JavaScript 文字列リテラル中では非法文字です:

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

ES2019 仕様は文字列リテラルが完全な JSON 文字セットをサポートすることを要求、つまりJavaScript は JSON の超集。ES2019 をサポートする環境中では、二重引用符/単一引用符中の U+2028U+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/flatMaptrimStart/trimEnd などのツール関数はすべて標準に組み込まれ、Object はまた一つどうでもいいメソッドが増え、Symbol はその説明情報を直接読み取れるようになりました

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

参考資料

コメント

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

コメントを書く