跳到主要內容
黯羽輕揚每天積累一點點

ES2019

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

一。概覽

2019 年 6 月發布了 ES2019 規範,即 ES10

包括 4 個新特性:

以及 6 個語法/語義上的變化:

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 做一對一的映射,而 flatMap 支持一對多(也可以對應 0 個)

例如:

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

相當於將每個元素映射成一個數組,最後再打平一層:

// 不考慮性能的話,可以這樣簡單實現
// const flatMap = (arr, f) => arr.map(f).flat();
// 或者
// const flatMap = (arr, f) => arr.reduce((a, v) => a.concat(f(v)), []);

主要有 2 個應用場景:

  • map + filter:返回空數組表示一對零,即 filter

  • 一對多映射

例如列出指定目錄下所有非隱藏文件:

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

// map + filter 結合 一對多映射
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.prototype.description 訪問 [創建 Symbol 時傳入的 description 參數](/articles/symbol-es6 筆記 7/#articleHeader3),例如:

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

語法上,支持兩種形式的 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 字符,不再拋出以上語法錯誤(正則表達式字面量中仍然不允許出現這兩個字符)

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)

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論