|
| 1 | +[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 增强的提案。 |
| 2 | + |
| 3 | +本周我们结合 [ES2022 feature: class static initialization blocks](https://2ality.com/2021/09/class-static-block.html) 这篇文章一起讨论一下这个特性。 |
| 4 | + |
| 5 | +## 概述 |
| 6 | + |
| 7 | +为什么我们需要 class static block 这个语法呢?其中一个原因是对 Class 静态变量的灵活赋值需求。以下面为例,我们想在 Class 内部对静态变量做批量初始化,就不得不写一个无用的 `_` 变量用来做初始化的逻辑: |
| 8 | + |
| 9 | +```typescript |
| 10 | +class Translator { |
| 11 | + static translations = { |
| 12 | + yes: 'ja', |
| 13 | + no: 'nein', |
| 14 | + maybe: 'vielleicht', |
| 15 | + }; |
| 16 | + static englishWords = []; |
| 17 | + static germanWords = []; |
| 18 | + static _ = initializeTranslator( // (A) |
| 19 | + this.translations, this.englishWords, this.germanWords); |
| 20 | +} |
| 21 | +function initializeTranslator(translations, englishWords, germanWords) { |
| 22 | + for (const [english, german] of Object.entries(translations)) { |
| 23 | + englishWords.push(english); |
| 24 | + germanWords.push(german); |
| 25 | + } |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +而且我们为什么把 `initializeTranslator` 写在外面呢?就因为在 Class 内部不能写代码块,但这造成一个严重的问题,是外部函数无法访问 Class 内部属性,所以需要做一堆枯燥的传值。 |
| 30 | + |
| 31 | +从这个例子看出,我们为了自定义一段静态变量初始化逻辑,需要做出两个妥协: |
| 32 | + |
| 33 | +1. 在外部定义一个函数,并接受大量 Class 成员变量传参。 |
| 34 | +2. 在 Class 内部定义一个无意义的变量 `_` 用来启动这个函数逻辑。 |
| 35 | + |
| 36 | +这实在太没有代码追求了,我们在 Class 内部做掉这些逻辑不就简洁了吗?这就是 class static block 特性: |
| 37 | + |
| 38 | +```typescript |
| 39 | +class Translator { |
| 40 | + static translations = { |
| 41 | + yes: 'ja', |
| 42 | + no: 'nein', |
| 43 | + maybe: 'vielleicht', |
| 44 | + }; |
| 45 | + static englishWords = []; |
| 46 | + static germanWords = []; |
| 47 | + static { // (A) |
| 48 | + for (const [english, german] of Object.entries(this.translations)) { |
| 49 | + this.englishWords.push(english); |
| 50 | + this.germanWords.push(german); |
| 51 | + } |
| 52 | + } |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +可以看到,`static` 关键字后面不跟变量,而是直接跟一个代码块,就是 class static block 语法的特征,在这个代码块内部,可以通过 `this` 访问 Class 所有成员变量,包括 `#` 私有变量。 |
| 57 | + |
| 58 | +原文对这个特性使用介绍就结束了,最后还提到一个细节,就是执行顺序。即所有 `static` 变量或区块都按顺序执行,父类优先执行: |
| 59 | + |
| 60 | +```typescript |
| 61 | +class SuperClass { |
| 62 | + static superField1 = console.log('superField1'); |
| 63 | + static { |
| 64 | + assert.equal(this, SuperClass); |
| 65 | + console.log('static block 1 SuperClass'); |
| 66 | + } |
| 67 | + static superField2 = console.log('superField2'); |
| 68 | + static { |
| 69 | + console.log('static block 2 SuperClass'); |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +class SubClass extends SuperClass { |
| 74 | + static subField1 = console.log('subField1'); |
| 75 | + static { |
| 76 | + assert.equal(this, SubClass); |
| 77 | + console.log('static block 1 SubClass'); |
| 78 | + } |
| 79 | + static subField2 = console.log('subField2'); |
| 80 | + static { |
| 81 | + console.log('static block 2 SubClass'); |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +// Output: |
| 86 | +// 'superField1' |
| 87 | +// 'static block 1 SuperClass' |
| 88 | +// 'superField2' |
| 89 | +// 'static block 2 SuperClass' |
| 90 | +// 'subField1' |
| 91 | +// 'static block 1 SubClass' |
| 92 | +// 'subField2' |
| 93 | +// 'static block 2 SubClass' |
| 94 | +``` |
| 95 | + |
| 96 | +所以 Class 内允许有多个 class static block,父类和子类也可以有,不同执行顺序结果肯定不同,这个选择权交给了使用者,因为执行顺序和书写顺序一致。 |
| 97 | + |
| 98 | +## 精读 |
| 99 | + |
| 100 | +结合提案来看,class static block 还有一个动机,就是给了一个访问私有变量的机制: |
| 101 | + |
| 102 | +```typescript |
| 103 | +let getX; |
| 104 | + |
| 105 | +export class C { |
| 106 | + #x |
| 107 | + constructor(x) { |
| 108 | + this.#x = { data: x }; |
| 109 | + } |
| 110 | + |
| 111 | + static { |
| 112 | + // getX has privileged access to #x |
| 113 | + getX = (obj) => obj.#x; |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +export function readXData(obj) { |
| 118 | + return getX(obj).data; |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +理论上外部无论如何都无法访问 Class 私有变量,但上面例子的 `readXData` 就可以,而且不会运行时报错,原因就是其整个流程都是合法的,最重要的原因是,class static block 可以同时访问私有变量与全局变量,所以可以利用其做一个 “里应外合”。 |
| 123 | + |
| 124 | +不过我并不觉得这是一个好点子,反而像一个 "BUG",因为任何对规定的突破都会为可维护性埋下隐患,除非这个特性用在稳定的工具、框架层,用来做一些便利性工作,最终提升了应用编码的体验,这种用法是可以接受的。 |
| 125 | + |
| 126 | +最后要意识到,class static block 本质上并没有增加新功能,我们完全可以用普通静态变量代替,只是写起来很不自然,所以这个特性可以理解为对缺陷的补充,或者是语法完善。 |
| 127 | + |
| 128 | +## 总结 |
| 129 | + |
| 130 | +总的来说,class static block 在 Class 内创建了一个块状作用域,这个作用域内拥有访问 Class 内部私有变量的特权,且这个块状作用域仅在引擎调用时初始化执行一次,是一个比较方便的语法。 |
| 131 | + |
| 132 | +原文下方有一些反对声音,说这是对 JS 的复杂化,也有诸如 JS 越来越像 Java 的声音,不过我更赞同作者的观点,也就是 Js 中 Class 并不是全部,现在越来越多代码使用函数式语法,即便使用了 Class 的场景也会存在大量函数申明,所以 class static block 这个提案对开发者的感知实际上并不大。 |
| 133 | + |
| 134 | +> 讨论地址是:[精读《class static block》· Issue #351 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/351) |
| 135 | +
|
| 136 | +**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** |
| 137 | + |
| 138 | +> 关注 **前端精读微信公众号** |
| 139 | +
|
| 140 | +<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg"> |
| 141 | + |
| 142 | +> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)) |
0 commit comments