Skip to main content

ES2019

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

Added some small features, made some corrections on syntax/semantics. In short, no major events in this update

I. Overview

ES2019 specification was released in June 2019, i.e., ES10

Includes 4 new features:

And 6 syntax/semantic changes:

  • Optional catch binding: Allow omitting the parameter part of catch block in try-catch structure

  • Array.prototype.sort: Requires sorting algorithm to be stable (equal elements maintain order before and after sorting)

  • Well-formed JSON.stringify: Requires JSON.stringify to return well-formed UTF-8 strings

  • JSON superset: Allow U+2028(LINE SEPARATOR) and U+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.entries ignores 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 throw SyntaxError)

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 Function constructor and other methods, also require toString() 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 Function constructor 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)

References

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment