一.概観
2019 年 6 月に ES2019 仕様、つまり ES10 がリリースされました
4 つの新特性が含まれます:
-
Array.prototype.{flat,flatMap}:配列を平坦化するために使用
-
String.prototype.{trimStart,trimEnd}:文字列 trim メソッドの標準化(広く実装されている非標準バージョンは
String.prototype.trimLeft/trimRightと呼ばれます) -
Symbol.prototype.description:Symbol の説明情報を返す
および 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)
コメントはまだありません