ECMAScript 2019(ES10)新特性

ECMAScript 2019(ES10)新特性:

  • 数组展平: Array.flat() Array.flatMap()
  • 数组稳定排序: Array.sort()
  • 把键值对列表转换为一个对象: Object.fromEntries()
  • 去掉字符串两端的空格: String.trimStart() String.trimEnd()
  • Symbol 新增属性: description()
  • JSON 被归为ECMAScript的子集, JSON.stringify()
  • try{} catch( error ) {} 参数 error 可以省略。
  • 修正: Function.toString()


Array.flat()、Array.flatMap()

Array.flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。 Array.flat() 把数组展平,通过传入层级深度参数(默认为 1),来为下层数组提升层级。如果想提升所有层级可以写一个比较大的数字甚至是 Infinity,但不推荐这么做。扩展:lodash 中的 _.flatten() 将数组展开一层。

[1, 2, [3, 4]].flat();
// [ 1, 2, 3, 4 ]

[1, 2, [3, 4, [5, 6]]].flat(2);
// [ 1, 2, 3, 4, 5, 6 ]

Array.flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map() 和深度值 1 的 flat() 几乎相同,但 flatMap() 通常在合并成一种方法的效率稍微高一些。 Array.flatMap() 它是 Array.map() Array.flat() 的组合,通过对 map() 调整后的数据尝试展平操作。

[1, 2, [3, 4]].flatMap(v => {
  if (typeof v === 'number') {
    return v * 2
  } else {
    return v.map(v => v * 2)
  }
})
// [2, 4, 6, 8]

Array.flatMap() 有个强大的功能是可以在 map() 的时候添加和删除元素,这个无论是 map() 还是 filter() 都没有这个功能。要想删除某一个元素只需要在 mapper 函数里面返回一个空的数组[],而增加元素只需在 mapper 函数里面返回一个长度大于 1 的数组,具体可以看下面的例子:

// 假如我们想要删除掉原数组里面所有的负数,同时将单数转换为一个复数和 1
const a = [5, 4, -3, 20, 17, -33, -4, 18]
// |\ \ x  |  | \  x  x  |
// [4,1, 4,  20, 16,1,     18]

a.flatMap(n =>
 (n < 0) ? []: // 删除负数
 (n % 2 == 0) ? [n] : // 保留复数
         [n - 1, 1]  // 单数变为一个复数和1
)
// [4, 1, 4, 20, 20, 16, 1, 18]


Array.sort()

Array.sort() :一定是个稳定的排序。所谓的稳定排序就是:假如没排序之前有两个相同数值的元素 a[i] a[j] ,而且 i j 前面,即 i < j ,经过排序后元素 a[i] 依然排在 a[j] 元素的前面,也就是说稳定的排序不会改变原来数组里面相同数值的元素的先后关系。

var users = [
  {name: 'Sean', rating: 14},
  {name: 'Ken', rating: 14},
  {name: 'Jeremy', rating: 13}
]
users.sort((a, b) => a.rating - b.rating)
// 非稳定的排序结果可能是
/*
[
    {name: 'Jeremy', rating: 13},
    {name: 'Ken', rating: 14},
    {name: 'Sean', rating: 14}
]
*/

// 虽然 Sean 和 Ken 具有同样的 rating,可是非稳定的排序不能保证他们两个的顺序在排序后保持不变。
ECMAScript2019 后,Array.sort() 将是一个稳定的排序,也就是说它可以保证 Sean 和 Ken 两个人的顺序在排序后不变。
/*
[
    {name: 'Jeremy', rating: 13},
    {name: 'Sean', rating: 14},
    {name: 'Ken', rating: 14}
]
*/


Object.fromEntries()

Object.fromEntries() 方法将一个 iterable 对象返回的一系列键值对转换为一个 Object。

const entriesArr = [['k1', 1], ['k2', 2]]
console.log(Object.fromEntries(entriesArr)
// {k1: 1, k2: 2}

const entriesMap = new Map([
 ['k1', 1],
 ['k2', 2]
]) 
// {"k1" => 1, "k2" => 2}
console.log(Object.fromEntries(entriesMap))
// {k1: 1, k2: 2}
const iteratorObj = {
 [Symbol.iterator]: function () {
  const entries = [['k1', 1], ['k2', 2]]
  let cursor = 0

  return {
   next() {
    const done = entries.length === cursor
    
    return {
     value: done ? undefined : entries[cursor++],
     done
    }
   }
  }
 }
}

Object.fromEntries(iteratorObj)
// {k1: 1, k2: 2}
const studentMap = {
 student1: {grade: 80},
 student2: {grade: 50},
 student3: {grade: 100}
}

const GoodStudentMap = Object.fromEntries(
 Object
  .entries(studentMap)
  .filter(([_, meta]) => meta.grade >= 60)
)

console.log(goodStudentMap)
// {student1: {grade: 80}, student3: {grade: 100}}


String.trimStart()、String.trimEnd()

String.trimStart() :将原字符串开头的空格,返回新的字符串。这个方法还有一个别名函数,叫做 String.trimLeft() ,它们具有一样的功能。

const greeting = '  Hello world! '
console.log(greeting.trimStart())
// 'Hello world! '

String.trimEndt() :将原字符串结尾的空格,返回新的字符串。这个方法还有一个别名函数,叫做 String.trimRight() ,它们具有一样的功能。

const greeting = ' Hello world! '
console.log(greeting.trimEnd())
// ' Hello world!'


Symbol.prototype.description()

ECMAScript2019 给 Symbol 对象添加了一个可选的 description 属性,这个属性是个只读属性。

console.log(Symbol('desc').description)
// desc
console.log(Symbol.for('desc').description)
// desc

// 一些内置的Symbol也有这个属性
console.log(Symbol.iterator.description)
// Symbol.iterator

// 如果初始化时没有带 description,这个属性会返回一个 undefined,因为这样才说这个属性是可选的
console.log(Symbol().description)
// undefined

// 这个属性是只读的,不能被设置
Symbol.iterator.description = 'mess it'
console.log(Symbol.iterator.description)
// Symbol.iterator

这个新的属性只要是为了方便开发者调试,不能通过比较两个 Symbol 对象的 description 来确定这两个 Symbol 是不是同一个 Symbol:

var s1 = Symbol("desc")
var s2 = Symbol("desc")
console.log(s1.description === s2.description)
// true
console.log(s1 === s2)
// false


Function.toString()

之前,调用 function 的 toString() 方法会将方法体里面的空格字符省略掉。

function hello() {
    console.log('hello Word')
}

console.log(hello.toString())
//'function hello() {\nconsole.log('hello word')\n}'

ECMAScript2019 之后,要求一定要返回函数源代码(保留空格字符)或者一个标准的占位符例如 native code,所以 ECMAScript2019 之后,以上的输出会变为。

console.log(hello.toString())
/*
function hello() {
    console.log('hello word')
}
*/


try/catch

在过去,try/catch 语句中的 catch 语句需要一个变量。 try/catch 语句帮助捕获终端级别的错误。

try {
  // Call a non-existing function undefined_Function
  undefined_Function("I'm trying");
}
catch(error) {
  // Display the error if statements inside try above fail
  console.log( error ); // undefined_Function is undefined
}

在 ES10 后,捕获错误的变量是可选的,现在可以跳过错误变量 error :

// ECMAScript2019 之前,你一定要在 catch 里面声明 error,否则会报错
try {
 ...
} catch (error) {

}

// 可是有时候,你确实用不到这个 error 对象,于是你会写这样的代码
try {
 ...
} catch (_) {
 ...
}

// ECMAScript2019后,你可以直接这样写了
try {
 ...
} catch {
 ...
}


JSON 被归为 ECMAScript 的子集

在之前,JSON 不是 ECMAScript 的子集,从而导致有些可以在 JSON 中包含的字符,不能够在 ECMAScript 的字面量中出现,比如 U+2028 和 U+2029

ECMAScript2019 后,这次改变之后,我们在编码的时候就不需要再去区分是 JSON 还是 ECMAScript 了。其对用户唯一的影响是保持原样,即在暂不支持特殊字符解析的运行环境中保持 SyntaxError 的报错。ES10 建议的解决方案是将未配对的代理代码点表示为 JSON 转义序列,而不是将它们作为单个 UTF-16 代码单元返回。

eval('\u2029'); // OK

const sourceCode = '"\u2028"';
eval(sourceCode); // SyntaxError

JSON.parse(json); // OK

格式良好的 JSON.stringify() 。此更新修复了字符 U+D800 到 U+DFFF 的处理,有时可以进入 JSON 字符串。这可能是一个问题,因为 JSON.stringify() 可能会将这些数字格式化为没有等效 UTF-8 字符的值,但 JSON 格式需要 UTF-8 编码。解析方法使用格式良好的JSON字符串,如

'{ "prop1" : 1, "prop2" : 2 }'; // A well-fORMed JSON format string