For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/proposal-Declarations-in-Conditionals.md.
  • 简体中文
  • Declarations in Conditionals S1

    中文标题:条件语句中的声明

    提案概览
    提案速览

    该提案允许开发者使用 letconstusingawait usingifwhile 语句的条件中声明变量。声明必须以显式的 ; 结尾,后跟一个真正被测试的第二个表达式,从而避免与现有解构语法产生歧义。这种模式只对初始化器求值一次,并使绑定仅存在于相关块的作用域内。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    条件语句中的声明

    ECMAScript 提案,允许在条件语句(如 if/while)内部进行变量声明。

    作者:

    • Devin Rousso

    阶段:1

    候选规范文本 可用。

    概述

    在使用 C++ 编程时,一个非常实用的特性是可以在计算条件之前,在条件内部声明变量:

    if (auto* ptr = getPtr(); ptr && ptr->value) {
        /* ... */
    }

    将这一能力添加到 JavaScript 中会非常有用,原因如下:

    • 避免多次求值初始化器
    • 对变量的可见性进行更细粒度的“控制”
    • 允许作者编写性能“安全”的代码,而不必了解被调用代码的具体细节(见下方示例)

    然而,就 JavaScript 而言,应该有一些限制:

    • 只能使用 letconstusingawait using
    • 仅用于 ifwhile
    • 仅在 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 表示由转译器选择的新鲜标签,因此它不会与源代码中的任何标签冲突。