前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES10 都出了,还没弄明白 ES6?

ES10 都出了,还没弄明白 ES6?

作者头像
ayqy贾杰
发布2019-12-02 13:38:29
6400
发布2019-12-02 13:38:29
举报
文章被收录于专栏:黯羽轻扬

感谢支持ayqy个人订阅号,每周义务推送1篇(only unique one)原创精品博文,话题包括但不限于前端、Node、Android、数学(WebGL)、语文(课外书读后感)、英语(文档翻译) 如果觉得弱水三千,一瓢太少,可以去 http://blog.ayqy.net 看个痛快

一.概览

2019 年 6 月发布了 ES2019 规范,即 ES10

包括 4 个新特性:

  • Array.prototype.{flat,flatMap}:用来打平数组
  • Object.fromEntries:Object.entries的逆运算
  • String.prototype.{trimStart,trimEnd}:规范化字符串 trim 方法(广泛实现的非规范版本叫String.prototype.trimLeft/trimRight
  • Symbol.prototype.description:返回 Symbol 的描述信息

以及 6 个语法/语义上的变化:

  • Optional catch binding:允许省略try-catch结构中catch块的参数部分
  • Array.prototype.sort:要求排序算法必须是稳定的(相等元素排序前后顺序不变)
  • Well-formed JSON.stringify:要求JSON.stringify返回格式良好的 UTF-8 字符串
  • JSON superset:字符串字面量中允许出现U+2028(LINE SEPARATOR)和U+2029(PARAGRAPH SEPARATOR)
  • Function.prototype.toString revision:要求返回 function 源码文本,或标准占位符

P.S.V8 v7.3+、Chrome 73+支持 ES2019 所有特性

二.Array.prototype.{flat,flatMap}

flat

代码语言:javascript
复制
Array.prototype.flat( [ depth ] )

即用来打平数组的flatten方法,支持一个可选的depth参数,表示打平指定层数(默认为 1):

代码语言:javascript
复制
[[1], [[2]], [[[3]]]].flat()
// 得到 [1, [2], [[3]]]
[[1], [[2]], [[[3]]]].flat(Infinity)
// 得到 [1, 2, 3]

简单实现如下:

代码语言:javascript
复制
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

代码语言:javascript
复制
Array.prototype.flatMap ( mapperFunction [ , thisArg ] )

P.S.可选参数thisArg用作mapperFunction中的this,例如:

代码语言:javascript
复制
[1, 2, 3, 4].flatMap(function(x) {
  return this.value ** x;
}, { value: 2 })
// 得到 [2, 4, 8, 16]

作用上,flatMapmap类似,主要区别在于:map做一对一的映射,而flatMap支持一对多(也可以对应 0 个)

例如:

代码语言:javascript
复制
[2, 0, 1, 9].flatMap(x => new Array(x).fill(x))
// 得到 [2, 2, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9]

相当于将每个元素映射成一个数组,最后再打平一层:

代码语言:javascript
复制
// 不考虑性能的话,可以这样简单实现
// const flatMap = (arr, f) => arr.map(f).flat();
// 或者
// const flatMap = (arr, f) => arr.reduce((a, v) => a.concat(f(v)), []);

主要有 2 个应用场景:

  • map + filter:返回空数组表示一对零,即 filter
  • 一对多映射

例如列出指定目录下所有非隐藏文件:

代码语言:javascript
复制
// 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):

代码语言:javascript
复制
const entries = Object.entries({ a: 1, b: 2 });
// 得到 [["a", 1], ["b", 2]]
const obj = Object.fromEntries(entries);
// 得到 {a: 1, b: 2}

类似于 lodash 提供的_.fromPairs(pairs),简单实现如下:

代码语言:javascript
复制
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,不限于数组
  • 只支持创建可枚举的、数据属性

例如:

代码语言:javascript
复制
// 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 掉

代码语言:javascript
复制
// 空白字符 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

例如:

代码语言:javascript
复制
'\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.

二者是别名关系,于是,有趣的事情发生了

代码语言:javascript
复制
String.prototype.trimLeft.name === 'trimStart'
String.prototype.trimRight.name === 'trimEnd'

五.Symbol.prototype.description

允许通过Symbol.prototype.description访问创建 Symbol 时传入的 description 参数,例如:

代码语言:javascript
复制
const mySymbol = Symbol('my description for this Symbol');
mySymbol.description === 'my description for this Symbol'

之前只能通过toString截取该描述信息:

代码语言:javascript
复制
mySymbol.toString().match(/Symbol\(([^)]*)\)$/)[1]

P.S.description属性是只读的:

Symbol.prototype.description is an accessor property whose set accessor function is undefined.

六.语法/语义变化

Optional catch binding

对于预料之中的异常,通常这样做:

代码语言:javascript
复制
try {
  JSON.parse('');
} catch(err) { /* noop */ }

没有用到err参数,但必须声明。因为省去参数的话,存在语法解析错误:

代码语言:javascript
复制
try {
  JSON.parse('');
} catch() { }
// 报错 Uncaught SyntaxError: Unexpected token )

而 ES2019 允许省略try-catch结构中catch块的参数部分:

Allow developers to use try/catch without creating an unused binding

语法上,支持两种形式的catch块:

代码语言:javascript
复制
// 带参数部分的catch块
catch( CatchParameter[?Yield, ?Await] ) Block[?Yield, ?Await, ?Return]
// 省略参数部分的catch块
catch Block[?Yield, ?Await, ?Return]

例如:

代码语言:javascript
复制
// 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).

例如:

代码语言:javascript
复制
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()时存在问题:

代码语言:javascript
复制
JSON.stringify('\uD800')
// 得到 '"�"'

实际上,JSON 支持\u形式的转义语法,所以 ES2019 要求JSON.stringify()返回格式正确的 UTF-8 编码字符串:

代码语言:javascript
复制
JSON.stringify('\uD800');
// 得到 '"\\ud800"'

算是对JSON.stringify()的 bug 修复

P.S.关于 JavaScript 中 Unicode 的更多信息,见JavaScript 中的 Unicode

JSON superset

字面量形式的(未经转义的)U+2028U+2029字符在 JSON 中是合法的,而在 JavaScript 字符串字面量中是非法字符:

代码语言:javascript
复制
const LS = "";
const PS = eval("'\u2029'");
// 报错 Uncaught SyntaxError: Invalid or unexpected token

ES2019 规范要求字符串字面量支持完整的 JSON 字符集,即JavaScript 作为 JSON 的超集。在支持 ES2019 的环境中,对于双引号/单引号中的U+2028U+2029字符,不再抛出以上语法错误(正则表达式字面量中仍然不允许出现这两个字符)

P.S.模板字符串不存在这个问题:

代码语言:javascript
复制
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] }",参数可以省略,并且内置方法要求给出方法名,例如:

代码语言:javascript
复制
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)

参考资料

  • ECMAScript® 2019 Language Specification
  • ECMAScript 2019: the final feature set
  • ES proposal: Object.fromEntries()
  • ES proposal: String.prototype.trimStart / String.prototype.trimEnd
  • ES proposal: optional catch binding
  • ES proposal: Function.prototype.toString revision
  • Well-formed JSON.stringify
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.概览
  • 二.Array.prototype.{flat,flatMap}
    • flat
      • flatMap
      • 三.Object.fromEntries
      • 四.String.prototype.{trimStart,trimEnd}
      • 五.Symbol.prototype.description
      • 六.语法/语义变化
        • Optional catch binding
          • Array.prototype.sort
            • Well-formed JSON.stringify
              • JSON superset
                • Function.prototype.toString revision
                • 七.总结
                  • 参考资料
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档