Declarations in Conditionals S1
中文标题:条件语句中的声明
提案速览
该提案允许开发者使用 let、const、using 和 await using 在 if 和 while 语句的条件中声明变量。声明必须以显式的 ; 结尾,后跟一个真正被测试的第二个表达式,从而避免与现有解构语法产生歧义。这种模式只对初始化器求值一次,并使绑定仅存在于相关块的作用域内。
Note
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
条件语句中的声明
ECMAScript 提案,允许在条件语句(如 if/while)内部进行变量声明。
作者:
阶段:1
候选规范文本 可用。
概述
在使用 C++ 编程时,一个非常实用的特性是可以在计算条件之前,在条件内部声明变量:
if (auto* ptr = getPtr(); ptr && ptr->value) {
/* ... */
}
将这一能力添加到 JavaScript 中会非常有用,原因如下:
- 避免多次求值初始化器
- 对变量的可见性进行更细粒度的“控制”
- 允许作者编写性能“安全”的代码,而不必了解被调用代码的具体细节(见下方示例)
然而,就 JavaScript 而言,应该有一些限制:
- 只能使用
let、const、using 和 await using
- 仅用于
if 和 while
- 仅在
if 块中暴露(即不在 else 中)
- 声明总是需要显式编写一个
;,后跟一个用于指定测试条件的第二个表达式
限制
在宽松模式下,以下代码不幸已经是合法的 JavaScript
var let = {};
var x = 0;
var y = [42];
if (let[x] = y) { // let[0] = 42
/* ... */
}
其中 let 被解析为标识符,因此 let[x] = y 是计算属性赋值,而不是解构声明。
如果允许 if (let x = y) 省略第二个表达式,开发者可能会合理地期望上面的形式是其对应的解构形式,尽管它现有含义不同。
// Create `x` and assign it to `y` and then explicitly check `x` for truthiness.
if (let x = y; x) { /* ... */ }
// Create `x` and assign it to `y` and then implicitly check `x` for truthiness.
if (let x = y) { /* ... */ }
// Create `x` and assign it to `y[0]` and then explicitly check `x` for truthiness.
if (let [x] = y; x) { /* ... */ }
// Create a property on the variable `let` with name equal to `x` and value equal to `y`.
if (let [x] = y) { /* ... */ }
这种期望会尤其自然,因为一旦存在显式编写的 ; 和第二个表达式,if (let x = y; x) 和 if (let [x] = y; x) 都会声明 x,然后测试随后的表达式。
要求每个声明都采用这种形式可以避免这种不一致。
示例
以下示例展示了允许在条件中进行声明可能很有用的场景:
class Foo {
get data() {
let result = [];
/* ... do some expensive work ... */
return result;
}
}
let foo = new Foo;
if (foo.data) {
for (let item of foo.data) {
/* A */
}
} else {
/* B */
}
可以被替换为
class Foo {
get data() {
let result = [];
/* ... do some expensive work ... */
return result;
}
}
let foo = new Foo;
if (let data = foo.data; data) {
for (let item of data) {
/* A */
}
} else {
/* B */
}
这种方式只对 foo.data 求值一次,同时将 data 的作用域限制在第一个分支中。
人们也可以创建另一个变量(例如 let data = foo.data;),但这可能会通过 data 使 foo.data 存留的时间远超需要,并且会用额外的变量“污染”作用域。
另一个示例是,非 module 的 <script> 需要一个额外的块来限制临时绑定的作用域:
<meta name="color-scheme" content="light dark">
<script>
{
const colorScheme = localStorage.getItem("color-scheme");
if (colorScheme) {
document.querySelector('meta[name="color-scheme"]').content = colorScheme;
}
}
</script>
如果可以将声明移动到条件中,则这个额外块就不再必要:
<meta name="color-scheme" content="light dark">
<script>
if (const colorScheme = localStorage.getItem("color-scheme"); colorScheme) {
document.querySelector('meta[name="color-scheme"]').content = colorScheme;
}
</script>
转译器支持
这可以使用块和生成的标签进行转译:
class Foo {
get data() {
let result = [];
/* ... do some expensive work ... */
return result;
}
}
let foo = new Foo;
__if0: {
{
let data = foo.data;
if (data) {
{
for (let item of data) {
/* A */
}
}
break __if0;
}
}
{
/* B */
}
}
请注意,__if0 表示由转译器选择的新鲜标签,因此它不会与源代码中的任何标签冲突。