[class-static-block](https://github.com/tc39/proposal-class-static-block) 提案于 [2021.9.1](https://github.com/tc39/proposal-class-static-block/commit/c0cabee0aa2d036a8d902fea7bc1d179e3de2477) 进入 stage4，是一个基于 Class 增强的提案。

本周我们结合 [ES2022 feature: class static initialization blocks](https://2ality.com/2021/09/class-static-block.html) 这篇文章一起讨论一下这个特性。

## 概述

为什么我们需要 class static block 这个语法呢？其中一个原因是对 Class 静态变量的灵活赋值需求。以下面为例，我们想在 Class 内部对静态变量做批量初始化，就不得不写一个无用的 `_` 变量用来做初始化的逻辑：

```typescript
class Translator {
  static translations = {
    yes: 'ja',
    no: 'nein',
    maybe: 'vielleicht',
  };
  static englishWords = [];
  static germanWords = [];
  static _ = initializeTranslator( // (A)
    this.translations, this.englishWords, this.germanWords);
}
function initializeTranslator(translations, englishWords, germanWords) {
  for (const [english, german] of Object.entries(translations)) {
    englishWords.push(english);
    germanWords.push(german);
  }
}
```

而且我们为什么把 `initializeTranslator` 写在外面呢？就因为在 Class 内部不能写代码块，但这造成一个严重的问题，是外部函数无法访问 Class 内部属性，所以需要做一堆枯燥的传值。

从这个例子看出，我们为了自定义一段静态变量初始化逻辑，需要做出两个妥协：

1. 在外部定义一个函数，并接受大量 Class 成员变量传参。
2. 在 Class 内部定义一个无意义的变量 `_` 用来启动这个函数逻辑。

这实在太没有代码追求了，我们在 Class 内部做掉这些逻辑不就简洁了吗？这就是 class static block 特性：

```typescript
class Translator {
  static translations = {
    yes: 'ja',
    no: 'nein',
    maybe: 'vielleicht',
  };
  static englishWords = [];
  static germanWords = [];
  static { // (A)
    for (const [english, german] of Object.entries(this.translations)) {
      this.englishWords.push(english);
      this.germanWords.push(german);
    }
  }
}
```

可以看到，`static` 关键字后面不跟变量，而是直接跟一个代码块，就是 class static block 语法的特征，在这个代码块内部，可以通过 `this` 访问 Class 所有成员变量，包括 `#` 私有变量。

原文对这个特性使用介绍就结束了，最后还提到一个细节，就是执行顺序。即所有 `static` 变量或区块都按顺序执行，父类优先执行：

```typescript
class SuperClass {
  static superField1 = console.log('superField1');
  static {
    assert.equal(this, SuperClass);
    console.log('static block 1 SuperClass');
  }
  static superField2 = console.log('superField2');
  static {
    console.log('static block 2 SuperClass');
  }
}

class SubClass extends SuperClass {
  static subField1 = console.log('subField1');
  static {
    assert.equal(this, SubClass);
    console.log('static block 1 SubClass');
  }
  static subField2 = console.log('subField2');
  static {
    console.log('static block 2 SubClass');
  }
}

// Output:
// 'superField1'
// 'static block 1 SuperClass'
// 'superField2'
// 'static block 2 SuperClass'
// 'subField1'
// 'static block 1 SubClass'
// 'subField2'
// 'static block 2 SubClass'
```

所以 Class 内允许有多个 class static block，父类和子类也可以有，不同执行顺序结果肯定不同，这个选择权交给了使用者，因为执行顺序和书写顺序一致。

## 精读

结合提案来看，class static block 还有一个动机，就是给了一个访问私有变量的机制：

```typescript
let getX;

export class C {
  #x
  constructor(x) {
    this.#x = { data: x };
  }

  static {
    // getX has privileged access to #x
    getX = (obj) => obj.#x;
  }
}

export function readXData(obj) {
  return getX(obj).data;
}
```

理论上外部无论如何都无法访问 Class 私有变量，但上面例子的 `readXData` 就可以，而且不会运行时报错，原因就是其整个流程都是合法的，最重要的原因是，class static block 可以同时访问私有变量与全局变量，所以可以利用其做一个 “里应外合”。

不过我并不觉得这是一个好点子，反而像一个 "BUG"，因为任何对规定的突破都会为可维护性埋下隐患，除非这个特性用在稳定的工具、框架层，用来做一些便利性工作，最终提升了应用编码的体验，这种用法是可以接受的。

最后要意识到，class static block 本质上并没有增加新功能，我们完全可以用普通静态变量代替，只是写起来很不自然，所以这个特性可以理解为对缺陷的补充，或者是语法完善。

## 总结

总的来说，class static block 在 Class 内创建了一个块状作用域，这个作用域内拥有访问 Class 内部私有变量的特权，且这个块状作用域仅在引擎调用时初始化执行一次，是一个比较方便的语法。

原文下方有一些反对声音，说这是对 JS 的复杂化，也有诸如 JS 越来越像 Java 的声音，不过我更赞同作者的观点，也就是 Js 中 Class 并不是全部，现在越来越多代码使用函数式语法，即便使用了 Class 的场景也会存在大量函数申明，所以 class static block 这个提案对开发者的感知实际上并不大。

> 讨论地址是：[精读《class static block》· Issue #351 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/351)

**如果你想参与讨论，请 [点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明：自由转载-非商用-非衍生-保持署名（[创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)）
