Skip to content

Scopes and Closures

Himxh edited this page Oct 4, 2026 · 1 revision

作用域与闭包 / Scopes and Closures

作用域是什么 / What a Scope Is

作用域决定变量在哪里可见。HimDSL 的作用域规则遵循 Java 的直觉:变量在声明它的花括号内可见。

Scope decides where a variable is visible. HimDSL follows Java intuition: a variable is visible inside the braces where it was declared.

int main() {
    int a = 1;                  // main 的整个作用域

    if (true) {
        int b = 2;              // 只在 if 块内
        a = a + b;              // ✓ a 可见
    }

    runcmd("CONSOLE", "say " + b);     // ✗ b 已出作用域
}

出了花括号,变量就不存在。

Outside the braces, the variable is gone.


函数作用域 / Function Scope

函数有自己的作用域。函数内部的变量函数外看不到,函数之间互不影响:

Functions have their own scope. Variables inside are invisible outside, and functions do not interfere:

void f() {
    int x = 10;
}

void g() {
    // x 在这里不存在
    int x = 20;                 // 这是 g 的 x,与 f 的无关
}

int main() {
    // x 在这里也不存在
}

同名变量在不同函数里是不同变量。这很自然,但初学者有时以为函数之间共享局部变量。

Same-named variables in different functions are distinct. Natural, but beginners sometimes assume locals are shared.


全局作用域 / Global Scope

在函数外声明的变量是全局的,所有函数都能访问:

Variables declared outside any function are global — visible to every function:

int counter = 0;

void increment() {
    counter += 1;               // 修改全局
}

int main() {
    increment();
    increment();
    runcmd("CONSOLE", "say " + counter);    // 2
}

全局变量适合需要跨函数共享的状态:计数器、配置、缓存。

Globals suit state shared across functions: counters, config, caches.

不要滥用全局变量。所有函数都能改它,出问题很难追踪。能用参数传的就传参数。

Do not overuse globals. Every function can modify them, hard to trace. Prefer parameters.


块作用域 / Block Scope

if、for、while 的花括号是新的作用域:

Braces in if, for, while create new scopes:

for (int i = 0; i < 3; i = i + 1) {
    // i 只在这个循环里
}

runcmd("CONSOLE", "say " + i);      // ✗ i 不存在

循环变量是块作用域的——这是好设计,避免循环结束后 i 还被引用。

Loop variables are block-scoped — a good design, avoiding accidental use after the loop.

嵌套块的可见性:

Nested blocks:

int main() {
    int outer = 1;

    for (int i = 0; i < 1; i = i + 1) {
        int inner = 2;
        outer += inner;         // ✓ 外层可见
        // i 在这里可见
    }

    // inner 不可见,i 不可见
    // outer 可见
}

内层能看外层,外层看不见内层。

Inner sees outer; outer cannot see inner.


lambda 闭包 / Lambda Closures

lambda 能访问定义位置可见的所有变量:

Lambdas access every variable visible at their definition site:

int main() {
    int base = 100;
    var f = (int x) -> x + base;        // 捕获 base

    runcmd("CONSOLE", "say " + f.apply(5));    // 105
}

闭包捕获的是变量,不是值。之后修改 base,lambda 看到的是新值:

Closures capture variables, not values. If base changes later, the lambda sees the new value:

int base = 100;
var f = (int x) -> x + base;

base = 200;
f.apply(5);                             // 205,不是 105

这一点容易让人意外。Java 里 lambda 只能捕获 final 变量,HimDSL 里可以捕获任意变量——代价是语义要记清楚。

This surprises many. Java lambdas capture only final variables; HimDSL allows any — the price is remembering the semantics.


闭包写回外层 / Writing Back Through Closures

lambda 里修改捕获的变量,修改会传回外层:

Modifications to captured variables propagate outward:

int counter = 0;
var bump = () -> {
    counter += 1;               // 修改外层的 counter
    return counter;
};

int main() {
    bump.apply();               // counter = 1
    bump.apply();               // counter = 2
    runcmd("CONSOLE", "say " + counter);    // 2
}

这实现了"计数器"模式。但也要小心:如果 lambda 在多处被调用,副作用会累积。

This enables "counters". But be careful: if the lambda is called from many places, side effects accumulate.


GUI 回调里的闭包 / Closures in GUI Callbacks

GUI 回调最常依赖闭包:

GUI callbacks rely on closures most:

int clickCount = 0;

var slot = new GUISlot(Material.DIAMOND);
slot.onLeftClick = () -> {
    clickCount += 1;            // 捕获 clickCount
    runcmd(@s.name, "msg 点击 " + clickCount + " 次");
    // 需要 gui 也捕获
    // gui.update(0, 4);
};

clickCount 在 main 或全局声明,onLeftClick 的 lambda 捕获它。每次点击都读写同一个变量。

clickCount is declared in main or globally; the lambda captures it. Each click reads and writes the same variable.

注意:如果 clickCount 是局部变量,且 main 已返回,lambda 依然能访问——HimDSL 的闭包会保持变量的存活。

Note: if clickCount is local and main has returned, the lambda still accesses it — HimDSL closures keep the variable alive.

int main() {
    int localCounter = 0;
    slot.onLeftClick = () -> {
        localCounter += 1;      // localCounter 虽然 main 已结束,但依然存在
    };
}

这是闭包的常见能力,Java / JavaScript / Python 里都一样。

A common closure behavior, present in Java / JavaScript / Python too.


枚举快照与恢复 / Enum Snapshots

HimDSL 的枚举作用域比普通变量更严格:进入块时快照,退出时恢复。

HimDSL's enum scope is stricter than normal variables: snapshot on block entry, restore on exit.

int main() {
    enum Color { RED };             // Color 有效

    if (true) {
        enum Size { SMALL };        // Size 只在这个 if 里
    }

    var s = Size.SMALL;             // ✗ Size 已恢复,不存在
}

枚举定义严格限定在块内。这防止枚举名在全局污染。

Enum definitions are strictly block-scoped. This prevents global pollution.

日常脚本中,枚举通常写在最外层——跨块使用更简单。

Daily scripts usually declare enums at the top level — simpler for cross-block use.


常见错误 / Common Mistakes

错误一:在循环外使用循环变量

for (int i = 0; i < 5; i = i + 1) { ... }
runcmd("CONSOLE", "say " + i);      // ✗ i 不存在

需要保留 i,在循环外先声明:

Declare outside to keep it:

int i = 0;
for (; i < 5; i = i + 1) { ... }
runcmd("CONSOLE", "say " + i);      // ✓

错误二:以为闭包捕获值

int n = 1;
var f = () -> n;
n = 2;
f.apply();                          // 返回 2,不是 1

错误三:靠全局变量传参

int temp;

void compute() {
    temp = 42;
}

int main() {
    compute();
    runcmd("CONSOLE", "say " + temp);
}

写 int compute() { return 42; } 更好。全局变量不必要。

Mistake 1: Loop variable outside the loop

Declare outside if needed after.

Mistake 2: Expecting capture-by-value

Closures capture variables, not values.

Mistake 3: Globals for parameter passing

Return values are cleaner.


小结 / Summary

  • 变量在声明它的花括号内可见。

  • 函数有独立作用域;函数外的变量是全局的。

  • 内层看外层,外层看不见内层。

  • lambda 捕获变量,不是值;修改会传回外层。

  • 闭包保持变量存活,即使函数已返回。

  • 枚举严格块作用域。

  • 少用全局变量,能用参数就用参数。

  • Variables are visible inside their declaring braces.

  • Functions have their own scope; top-level variables are global.

  • Inner sees outer; outer cannot see inner.

  • Lambdas capture variables, not values; writes propagate.

  • Closures keep variables alive after function return.

  • Enum definitions are strictly block-scoped.

  • Minimize globals; pass parameters when you can.


下一步 / Next Steps

下一章 [调试技巧 / Debugging] 讲怎么快速定位脚本问题。

Next, [调试技巧 / Debugging] covers locating script problems fast.


HimDSL 文档 / Documentation

入门 / Getting Started

语言基础 / Language Basics

控制流 / Control Flow

函数 / Functions

数据结构 / Data Structures

自定义类型 / Custom Types

Minecraft 集成 / Minecraft Integration

事件与调度 / Events & Scheduling

GUI 系统 / GUI System

内置函数参考 / Built-ins Reference

进阶 / Advanced

扩展 API / Extension API

你真的需要 API 吗?/ Do you really need the API?

Clone this wiki locally