image.png

ES2020已经发布,接下来将开始制定ES2021。本文将介绍截止2020年7月,已经进入TC39-Finished(Stage 4)阶段的提案TC39-Candidate(Stage 3)的提案。

阿里巴巴前端委员会去年成为了Ecma官方成员,可以推进和阻止TC组织中的议题。但TC组织中的议题一旦进入Stage 2,其实就很难再阻止,除非是发现了重大的设计缺陷。TC39 7月20号会议agenda

Finished篇(Stage 4)

Finished(Stage 4)阶段的特性已经成为 ECMAScript 官方标准。这些特性基本不会再修改了,但并不是指已经可以大面积或者在生产环境中使用。而是意味着浏览器厂商已经部分实现,但可能还有很多地方需要打磨,具体的覆盖率可以通过 Can I Use 搜索查看。

Optional Chaining (?.) && Nullish Coalescing (??)

TC39提议链接:https://github.com/tc39/proposal-optional-chaining && https://github.com/tc39/proposal-nullish-coalescing
特性介绍:之所以将这2个特性合并一起讲,是因为他们的使用场景都是针对配置文件的处理:解决链式调用undefined查询配置默认值。这2个特性看代码更容易理解。
支持度:Optional chaining Global 77.49%Nullish coalescing Global 77.54%

代码示例:

  1. /*
  2. config: {
  3. mysql: {
  4. server: {
  5. address: '192.168.172.155:3306',
  6. user: 'admin',
  7. pwd: 'pwd123',
  8. debug: false
  9. },
  10. client: {}
  11. }
  12. }
  13. */
  14. // 从配置文件中读取mysql链接方式
  15. let mysqlConfig = config.get("mysql");
  16. let address = mysqlConfig?.server?.address ?? '127.0.0.1:3306';
  17. let user = mysqlConfig?.server?.user ?? 'admin';
  18. let password = mysqlConfig?.server?.pwd ?? 'pwd123';
  19. let needDebug = mysqlConfig?.server?.debug ?? true;
  20. const mysqlCLient = await connectMysql(address, {user, password, debug: needDebug});

BigInt

TC39提议链接:https://github.com/tc39/proposal-bigint
特性介绍:大整数特性让JS也支持高精度计算,超出JS的原生整数类型的精度安全边界运算。由Bloomberg提议,由Igalia推动,金融产品通常比较需要”大整数”支持。期待未来可以支持BigDecimal,解决JS一直被诟病的0.1+0.2 !== 0.3的问题。
支持度:Global 73.12%

代码示例:

let maxInt = Number.MAX_SAFE_INTEGER;// 9007199254740991
++maxInt;// 9007199254740992
++maxInt;// 9007199254740992
++maxInt;// 9007199254740992

let bigInt = 9007199254740991n;
++bigInt;// 9007199254740992n
++bigInt;// 9007199254740993n
bigInt*bigInt;// 81129638414606699710187514626049n

Promise.allSettled

TC39提议链接:https://github.com/tc39/proposal-promise-allSettled
特性介绍:对比Promise.all需要数组中的Promise全部resolved, Promise.allSettled 的数组中resolved 或 rejected 状态都可以,只要全部返回结果。
支持度:Global 81.53%

代码示例:

const settlePromiseArray = [ Promise.resolve(100), Promise.reject(null), Promise.reject(new Error('err'))];
Promise.allSettled(settlePromiseArray).then(results => {
    console.log(results);
});
// [{"status":"fulfilled","value":100, {"status":"rej…"reason":null}, {"status":"rejected","reason":{}}]

globalThis

TC39提议链接:https://github.com/tc39/proposal-global
特性介绍:SSR的福音,在浏览器中全局变量window和Node.js全局变量global,可以通过globalThis统一。当然浏览器和Node.js特有的接口,globalThis不会同时支持。
支持度:Global 87.37%

代码示例:

// 浏览器中
globalThis.setTimeout === window.setTimeout;

// Node.js中
globalThis.setTimeout === global.setTimeout;

dynamic import

TC39提议链接:https://github.com/tc39/proposal-dynamic-import
特性介绍:JS中将支持动态import,import可以作为一个函数加载模块,支持then, catch链式调用。
支持度:Global 89.02%

代码示例:

/*
<!DOCTYPE html>
<nav>
  <a href="books.html" data-entry-module="books">Books</a>
  <a href="movies.html" data-entry-module="movies">Movies</a>
  <a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>
<main>Content will load here!</main>

*/
const main = document.querySelector("main");
for (const link of document.querySelectorAll("nav > a")) {
link.addEventListener("click", e => {
  e.preventDefault();

  import(`./section-modules/${link.dataset.entryModule}.js`)
    .then(module => {
      module.loadPageInto(main);
    })
    .catch(err => {
      main.textContent = err.message;
    });
});
}

Well defined for-in order

TC39提议链接:https://github.com/tc39/proposal-for-in-order
特性介绍:ES2020官方指定了for in的遍历顺序(之前并没有)。

Candidate篇(Stage 3)

Candidate阶段的特性确定需要实现,但要实现方和用户反馈打磨。在浏览器的先行版本和测试版本中,部分可用。

Top-level await

TC39提议链接:https://github.com/tc39/proposal-top-level-await
特性介绍:在顶级作用域直接使用await,看似简单的特性却影响了很多使用JS的场景。例如:import动态依赖路径;资源初始化;资源依赖回滚。另外如果模块依赖中使用了await,意味着后续的模块加载都会被block,也会block JS执行。
副作用:block JS执行;block 资源加载;无法在CommonJS modules中使用;同级模块并无法block加载;await发生在模块执行阶段,此时模块都已经获取和链接完毕;

代码示例:

传统的做法ES2015:

await Promise.resolve(console.log('🎉'));
// → SyntaxError: await is only valid in async function

(async function() {
  await Promise.resolve(console.log('🎉'));
  // → 🎉
}());

ES2020中:

await Promise.resolve(console.log('🎉'));
// → 🎉

// import动态依赖路径;
const strings = await import(`/i18n/${navigator.language}`);
// 资源初始化
const connection = await dbConnector();
// 资源依赖回滚
let jQuery;
try {
  jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.example.com/jQuery');
}

WeakRefs && FinalizationRegistry

TC39提议链接:https://github.com/tc39/proposal-weakrefs
特性介绍:首先尽可能避免使用WeakRefFinalizationRegistryWeakRefFinalizationRegistry可以分开使用也可以一起使用。WeakRef实例不会阻止GC回收,但是GC会在2次EventLoop之间回收”WeakRef实例”。GC回收后WeakRef实例的deref()方法将返回”undefined”。FinalizationRegistry注册Callback,某个对象被GC回收后调用。
副作用:JS引擎的GC很复杂,可能并不能如我们预期的回收对象。

代码示例:

let gListenerRegistry = new FinalizationRegistry(
    ({ socket, wrapper }) => socket.removeEventListener("message", wrapper);
);
function addWeakListener(socket, listener) {
    let weakRef = new WeakRef(listener);
    let wrapper = (ev) => { weakRef.deref()?.(ev); };
    gListenerRegistry.register(listener, { socket, wrapper });
    socket.addEventListner("message", wrapper);
}
class MovingAvg {
    constructor(socket) {
        this.events = [];
        this.listener = (ev) => { this.events.push(ev); };
        addWeakListener(socket, this.listener);
    }
}

Private instance methods and accessors

TC39提议链接:https://github.com/tc39/proposal-private-methods
特性介绍:JS的Class引入了语言级别的私有属性,可以不用闭包来模拟了。JS作为面向原型语言,但随着TC39的不断推进,在向着更加成熟的语言进阶。

代码示例:

ES2015 只能在构造器中初始化this.xValue

class Counter extends HTMLElement {
  get x() { return this.xValue; }
  set x(value) {
    this.xValue = value;
    window.requestAnimationFrame(this.render.bind(this));
  }

  clicked() {
    this.x++;
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
    this.xValue = 0;
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.x.toString();
  }
}
window.customElements.define('num-counter', Counter);

ES2020 可以直接定义属性

class Counter extends HTMLElement {
  xValue = 0;

  get x() { return this.xValue; }
  set x(value) {
    this.xValue = value;
    window.requestAnimationFrame(this.render.bind(this));
  }

  clicked() {
    this.x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.x.toString();
  }
}
window.customElements.define('num-counter', Counter);

ES2020 可以用#符号直接定义私有属性

class Counter extends HTMLElement {
  #xValue = 0;

  get #x() { return #xValue; }
  set #x(value) {
    this.#xValue = value;
    window.requestAnimationFrame(this.#render.bind(this));
  }

  #clicked() {
    this.#x++;
  }

  constructor() {
    super();
    this.onclick = this.#clicked.bind(this);
  }

  connectedCallback() { this.#render(); }

  #render() {
    this.textContent = this.#x.toString();
  }
}
window.customElements.define('num-counter', Counter);

参考文献

  1. https://v8.dev/features/top-level-await#dynamic-dependency-pathing
  2. https://v8.dev/features/bigint
  3. https://caniuse.com/
  4. https://www.freecodecamp.org/news/javascript-new-features-es2020/
  5. https://tc39.es/ecma262/