Repository navigation
Scopes and Closures
作用域决定变量在哪里可见。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.
函数有自己的作用域。函数内部的变量函数外看不到,函数之间互不影响:
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.
在函数外声明的变量是全局的,所有函数都能访问:
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.
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 能访问定义位置可见的所有变量:
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.
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 回调最常依赖闭包:
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.
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.
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; } 更好。全局变量不必要。
Declare outside if needed after.
Closures capture variables, not values.
Return values are cleaner.
-
变量在声明它的花括号内可见。
-
函数有独立作用域;函数外的变量是全局的。
-
内层看外层,外层看不见内层。
-
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.
下一章 [调试技巧 / Debugging] 讲怎么快速定位脚本问题。
Next, [调试技巧 / Debugging] covers locating script problems fast.
- 首页 / Home
- 安装与快速开始 / Installation & Quick Start
- 第一个脚本 / Your First Script
- 命令速查 / Command Reference
- 自动执行脚本 / Auto-run Scripts
- 定义函数 / Defining Functions
- 函数重载 / Function Overloading
- Lambda 表达式 / Lambda Expressions
- 函数式接口 / Functional Interfaces
- 选择器总览 / Selectors Overview
- 选择器过滤器 / Selector Filters
- EntityRef 引用 / EntityRef
- 占位符参考 / Placeholders Reference
- PlaceholderAPI 桥接 / PAPI Bridge
- 创建 GUI / Creating a GUI
- GUISlot 槽位 / GUISlot
- 点击回调 / Click Callbacks
- onClose 回调 / onClose Callback
- GUI 刷新 / GUI Refresh
- 数学函数 / Math Built-ins
- 高精度计算 / BigDecimal Built-ins
- 字符串函数 / String Built-ins
- 集合函数 / Collection Built-ins
- 文件 I/O / File I/O
- 命令与 sleep / Commands & sleep