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

ES2019

免費2019-09-28#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]

作用上,flatMapmap 類似,主要區別在於: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+2028U+2029 字符在 JSON 中是合法的,而在 JavaScript 字符串字面量中是非法字符:

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

ES2019 規範要求字符串字面量支持完整的 JSON 字符集,即JavaScript 作為 JSON 的超集。在支持 ES2019 的環境中,對於雙引號/單引號中的 U+2028U+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/flatMaptrimStart/trimEnd 等工具函數都已經納入標準,Object 又增加了一個無關緊要的方法,Symbol 支持直接讀取其描述信息了

此外,語法/語義上還做了一些修正,允許��略 catch 塊的參數部分,要求數組 sort() 必須穩定排序,明確了函數 toString() 的具體實現,完善了 JSON 支持,期望成為 JSON 的超集(JSON ? ECMAScript)

參考資料

評論

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

提交評論