I. Overview
ES2019 specification was released in June 2019, i.e., ES10
Includes 4 new features:
-
Array.prototype.{flat,flatMap}: Used to flatten arrays
-
Object.fromEntries: Inverse operation of Object.entries
-
String.prototype.{trimStart,trimEnd}: Standardize string trim methods (widely implemented non-standard versions are called
String.prototype.trimLeft/trimRight) -
Symbol.prototype.description: Return Symbol's description information
And 6 syntax/semantic changes:
-
Optional catch binding: Allow omitting the parameter part of
catchblock intry-catchstructure -
Array.prototype.sort: Requires sorting algorithm to be stable (equal elements maintain order before and after sorting) -
Well-formed JSON.stringify: Requires
JSON.stringifyto return well-formed UTF-8 strings -
JSON superset: Allow
U+2028(LINE SEPARATOR) andU+2029(PARAGRAPH SEPARATOR) in string literals -
Function.prototype.toString revision: Requires returning function source text, or standard placeholder
P.S. V8 v7.3+, Chrome 73+ support all ES2019 features
II. Array.prototype.{flat,flatMap}
flat
Array.prototype.flat( [ depth ] )
This is the flatten method used to flatten arrays, supports an optional depth parameter, indicating how many levels to flatten (default is 1):
[[1], [[2]], [[[3]]]].flat()
// Gets [1, [2], [[3]]]
[[1], [[2]], [[[3]]]].flat(Infinity)
// Gets [1, 2, 3]
Simple implementation as follows:
const flat = (arr, depth = 1) => {
if (depth > 0) {
const flated = Array.prototype.concat.apply([], arr);
// Or
// 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. Optional parameter thisArg is used as this in mapperFunction, for example:
[1, 2, 3, 4].flatMap(function(x) {
return this.value ** x;
}, { value: 2 })
// Gets [2, 4, 8, 16]
In function, flatMap is similar to map, main difference is: map does one-to-one mapping, while flatMap supports one-to-many (can also correspond to 0)
For example:
[2, 0, 1, 9].flatMap(x => new Array(x).fill(x))
// Gets [2, 2, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9]
Equivalent to mapping each element to an array, then flattening one level:
// If not considering performance, can simply implement like this
// const flatMap = (arr, f) => arr.map(f).flat();
// Or
// const flatMap = (arr, f) => arr.reduce((a, v) => a.concat(f(v)), []);
Mainly has 2 application scenarios:
-
map + filter: Return empty array represents one-to-zero, i.e., filter
-
One-to-many mapping
For example, list all non-hidden files in specified directory:
// node 12.10.0
const fs = require('fs');
const path = require('path');
// map + filter combined with one-to-many mapping
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;
});
III. Object.fromEntries
Object.fromEntries ( iterable )
Used to convert a set of key-value pairs to object, equivalent to Object.entries inverse operation, used to fill the gap in data type conversion (key-value pairs to Object):
const entries = Object.entries({ a: 1, b: 2 });
// Gets [["a", 1], ["b", 2]]
const obj = Object.fromEntries(entries);
// Gets {a: 1, b: 2}
Similar to lodash's _.fromPairs(pairs), simple implementation as follows:
const fromEntries = pairs => pairs.reduce((acc, [ key, val ]) => Object.assign(acc, { [key]: val }), {});
P.S. Official polyfill see es-shims/Object.fromEntries
Special cases:
-
If there are key-value pairs with same key, later ones overwrite earlier ones
-
Supports using Symbol as key (while
Object.entriesignores Symbol key) -
Non-String/Symbol type keys in key-value pairs will be forcibly converted to String
-
Parameter supports iterable, not limited to arrays
-
Only supports creating enumerable, data properties
For example:
// 1. If there are key-value pairs with same key, later ones overwrite earlier ones
Object.fromEntries([['a', 1], ['b', 2], ['a', 3]]);
// Gets {a: 3, b: 2}
// 2. Supports using Symbol as key (while `Object.entries` ignores Symbol key)
Object.fromEntries([[Symbol('a'), 1], ['b', 2]]);
// Gets {b: 2, Symbol(a): 1}
// 3. Non-String/Symbol type keys in key-value pairs will be forcibly converted to String
Object.fromEntries([[new Error('here'), 1], [{}, 2]]);
// Gets {['Error: here']: 1, ['[object Object]']: 2}
// 4. Parameter supports iterable, not limited to arrays
Object.fromEntries(function*(){
yield ['a', 1];
yield ['b', 2];
}());
// Gets {a: 1, b: 2}
// 5. Only supports creating enumerable, data properties
Object.getOwnPropertyDescriptors(Object.fromEntries([['a', 1]]))
// Gets { a: {value: 1, writable: true, enumerable: true, configurable: true} }
IV. String.prototype.{trimStart,trimEnd}
Considered standard definition of trimLeft/trimRight, naming is to maintain consistency with ES2017's padStart/padEnd
In function, whitespace characters and line terminators will be trimmed:
// Whitespace 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
// ...and other Unicode characters with White_Space property under Space_Separator category
// Line terminators https://tc39.github.io/ecma262/#sec-line-terminators
'\u000A' // <LF> LINE FEED
'\u000D' // <CR> CARRIAGE RETURN
'\u2028' // <LS> LINE SEPARATOR
'\u2029' // <PS> PARAGRAPH SEPARATOR
For example:
'\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
Additionally, for backward compatibility, trimLeft/trimRight are still retained, defined in specification Annex B (B Additional ECMAScript Features for Web Browsers, requires Web browsers to implement), but recommend using 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.
The two are alias relationships, so, interesting things happened:
String.prototype.trimLeft.name === 'trimStart'
String.prototype.trimRight.name === 'trimEnd'
V. Symbol.prototype.description
Allow accessing [description parameter passed when creating Symbol](/articles/symbol-es6 笔记 7/#articleHeader3) through Symbol.prototype.description, for example:
const mySymbol = Symbol('my description for this Symbol');
mySymbol.description === 'my description for this Symbol'
Previously could only截取 this description information through toString:
mySymbol.toString().match(/Symbol\(([^)]*)\)$/)[1]
P.S. description property is read-only:
Symbol.prototype.description is an accessor property whose set accessor function is undefined.
VI. Syntax/Semantic Changes
Optional catch binding
For anticipated exceptions, usually do this:
try {
JSON.parse('');
} catch(err) { /* noop */ }
Didn't use err parameter, but must declare it. Because omitting the parameter causes syntax parsing error:
try {
JSON.parse('');
} catch() { }
// Errors Uncaught SyntaxError: Unexpected token )
And ES2019 allows omitting the parameter part of catch block in try-catch structure:
Allow developers to use try/catch without creating an unused binding
Syntactically, supports two forms of catch blocks:
// catch block with parameter part
catch( CatchParameter[?Yield, ?Await] ) Block[?Yield, ?Await, ?Return]
// catch block omitting parameter part
catch Block[?Yield, ?Await, ?Return]
For example:
// node 12.10.0
const parseJSON = (str = '') => {
let json;
try {
json = JSON.parse(str);
} catch {
consle.error('parseJSON error, just ignore it.');
}
};
parseJSON('');
// Outputs parseJSON error, just ignore it.
Theoretically, exception information in most scenarios should not be ignored (either record it, or throw it out, or find a way to clean up), relatively reasonable scenarios include:
-
assert.throws(func): Used for test-driven libraries, assert executing specified function will throw exception (don't care what kind of exception) -
Browser feature detection: Just want to know whether specific feature is supported
-
Cleanup measure exceptions: For example,
logError()itself has exceptions, even if can capture, nothing can be done
P.S. Even in these scenarios, when deciding to ignore an exception, should also explain the reason in comments
Array.prototype.sort
Requires must be stable sorting (relative order of equal elements remains unchanged before and after sorting):
The sort must be stable (that is, elements that compare equal must remain in their original order).
For example:
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(' '));
// Expected result is I feeling am lucky
// Not feeling I am lucky
Well-formed JSON.stringify
JSON specification requires widely used JSON should be encoded with UTF-8:
JSON text exchanged between systems that are not part of a closed ecosystem MUST be encoded using UTF-8.
And in JavaScript, for half surrogate pairs appearing alone, there's a problem with JSON.stringify():
JSON.stringify('\uD800')
// Gets '"?"'
Actually, JSON supports \u form escape syntax, so ES2019 requires JSON.stringify() to return well-formed UTF-8 encoded strings:
JSON.stringify('\uD800');
// Gets '"\\ud800"'
Considered as a bug fix for JSON.stringify()
P.S. For more information about Unicode in JavaScript, see Unicode in JavaScript
JSON superset
Literal form (unescaped) U+2028 and U+2029 characters are legal in JSON, but are illegal characters in JavaScript string literals:
const LS = "?";
const PS = eval("'\u2029'");
// Errors Uncaught SyntaxError: Invalid or unexpected token
ES2019 specification requires string literals to support complete JSON character set, i.e., JavaScript as JSON's superset. In environments supporting ES2019, for U+2028 and U+2029 characters in double quotes/single quotes, no longer throw above syntax errors (still not allowed in regular expression literals)
P.S. [Template strings](/articles/模板字符串-es6 笔记 3/#articleHeader5) don't have this problem:
const LS = `?`;
const PS = eval("`\u2029`");
Function.prototype.toString revision
Requires returning function source text, or standard placeholder:
implementations must not be required to retain source text for all functions defined using ECMAScript code
Specifically:
-
If function is created through ES code,
toString()must return its source code -
If
toString()cannot get legal ES code, return standard placeholder, placeholder string must not be legal ES code (eval(placeholder)must throwSyntaxError)
P.S. Specification suggests placeholder form as "function" BindingIdentifier? "(" FormalParameters ")" "{ [native code] }", parameters can be omitted, and built-in methods require giving method name, for example:
document.createAttribute.toString()
// Outputs "function createAttribute() { [native code] }"
Special cases:
-
Function source code returned by
toString()is not necessarily legal, may only be legal in its lexical context -
Functions dynamically created through
Functionconstructor and other methods, also requiretoString()to return appropriate source code// 1.
toString()return value may only be legal in its lexical context class C { foo() { /hello/ } } const source = C.prototype.foo.toString(); eval(source) // Errors Uncaught SyntaxError: Unexpected token {// 2. Functions dynamically created through
Functionconstructor and other methods also support new Function('a', 'b', 'return a + b;').toString() // Outputs function anonymous(a,b) { return a + b; }
VII. Summary
flat/flatMap, trimStart/trimEnd and other utility functions have been incorporated into standards, Object added another unimportant method, Symbol supports directly reading its description information
Additionally, made some corrections on syntax/semantics, allow omitting catch block's parameter part, require array sort() must be stable sorting, clarified function toString() specific implementation, improved JSON support, expect to become JSON's superset (JSON ? ECMAScript)
No comments yet. Be the first to share your thoughts.