# v1.1 到 v2.0 的更改

## 目录

语言

- 已删除旧式语法
- 热键和热字串标签
- 名称
- 作用域
- 变量
- 表达式
- 对象(其他)
- 函数
- 套嵌函数
- 未分类
- 延续片段
- 延续行
- 类型

对象

- 原始值
- 属性和方法
- 静态变量
- 元函数
- 数组
- Map
- 枚举器
- 函数
- COM 对象(ComObject)
- 默认属性
- Com Calls

库

- 修改的命令/函数
- 新函数
- Static/Class 变量
- 新指令
- 内置变量
- 内置对象

Gui

- Gui 子命令
- 事件
- 删除的
- 控件选项
- GuiControlGet
- GuiControl
- 其他更改

错误处理

- 可继续错误
- ErrorLevel
- 表达式
- 函数
- Catch

键盘, 鼠标, 热键和热字串

其他

- 持续运行
- 线程
- 默认设置
- 命令行

## 语言

### 已删除旧式语法

删除原义(文本) 赋值: `var = value`

删除了所有旧式 If 语句, 仅保留 `if 表达式`, 它从不要求有括号(但允许有括号, 就像在任何表达式中一样).

删除 "命令语法". 已经没有了 "命令", 只有 函数调用语句, 它们只是没有括号的函数或方法调用. 这意味着:

- 所有以前的命令现在都是函数(不包括控制流语句).
- 如果不需要返回值, 所有的函数都可以不带括号调用(但是和以前一样, 表达式内的调用不能省略括号).
- 所有参数都是表达式, 因此所有文本都是 "带引号的", 逗号不需要转义. 目前这不包括一些指令(它们既不是命令也不是函数).
- 不管是否有括号, 参数都是一样的; 也就是说, 返回值没有输出变量, 因此如果省略括号, 它将被丢弃.
- 正常的变量引用从不用百分号括起来(除了 #Include 和 #DllLoad). 使用连接或 Format 来包含文本中的变量.
- 在函数名和参数之间没有逗号, 所以 `MouseGetPos(, y)` = `MouseGetPos , y` (省略 x). 为了清楚起见, 需要一个空格或制表符. 为了保持一致性, 指令也遵循新的约定(指令名称和参数之间不能有逗号).
- 没有百分号-空格前缀来强制表达式. 表达式中没有引号的百分号只用于双解引/动态引用, 有奇数个百分号是一个语法错误.
- 方法调用语句(省略括号的方法调用) 被限制为一个普通的变量, 后面跟着一个或多个用点分隔的标识符, 如 `MyVar.MyProperty.MyMethod "String to pass"`.

从 v1 命令到函数的转换通常如下所示(但有些函数已经改变了, 下文会进一步说明):

- 如果命令的第一个参数是一个输出变量, 而第二个参数不是, 它就会成为返回值, 并从参数列表中删除.
- 其余的输出变量的处理方式与 ByRef 参数类似(其用法和语法已经改变), 只是它们允许引用可写的内置变量.
- 失败时抛出异常, 而不是设置 ErrorLevel.
- 以前通过 ErrorLevel 返回的值会通过其他方式返回, 替换为异常, 作废或者无返回值.

所有控制流语句也接受表达式, 除了下面提到的.

所有带参数的控制流语句(目前不包括两个单词的 Loop 语句) 都支持小括号包围其参数列表, 名称和小括号之间没有任何空格. 例如, `return(var)`. 但是, 这些不是函数; 例如, `x := return(y)` 是无效的. If 和 While 已经支持这个.

Loop(除了 Loop Count) 后面现在有一个次要的关键字(Files, Parse, Read 或 Reg), 它不能被 "括起来" 或被变量包含. 目前, 关键字后面可以有一个逗号, 但这不是必须的, 因为这不是一个参数. OTB 被所有模式所支持.

Goto, break 和 continue 需要一个没有引号的标签名, 类似于 v1(`goto label` 跳转到 `label:`). 要动态地跳转到一个标签, 请在名称后面使用圆括号: `goto(expression)`. 然而, 这不是一个函数, 不能在表达式中间使用. 圆括号可以与 break 或 continue 一起使用, 但在这种情况下, 参数必须是一个单一的原义数字或带引号的字符串.

Gosub 已被删除, 标签也不能再与 SetTimer 和 Hotkey 等函数一起使用.

- 它们是多余的; 基本上只是一种有限的函数形式, 没有局部变量或返回值, 而且是在它们自己的独立命名空间中. 函数可以在以前使用标签子程序的任何地方使用(甚至在其他函数内部).
- 函数不能重叠(但可以包含在一个函数中). 相反, 可以使用多个函数, 并从其他函数中调用. 使用函数参数取代 A_ThisLabel.
- 与子程序不同, 如果忘记定义函数的 结尾, 通常会被提醒错误, 因为每个 `{` 必须有一个相应的 `}`. 与标签子程序相比, 识别一个函数的边界可能也更容易.
- 函数可以放在自动执行部分而不中断它. 自动执行部分现在可以很容易地跨越整个脚本, 所以可以改称为全局代码, 在自动执行线程中执行.
- 函数可能不太容易像 "goto" 一样被误用(用户循环跳转到当前子程序(循环子程序本身), 不可避免地耗尽堆栈空间并终止程序).
- 如果没有接受标签或函数的函数(如 Hotkey), 就不会有那么多的歧义, 因为两者可以同时以相同的名称存在.
- 对于标签的所有其余用途, 从一个函数内部引用一个全局标签是无效的. 因此, 标签查找可以被限制在局部标签列表中. 因此, 没有必要检查从函数内部到外部的无效跳转(从未支持过).

### 热键和热字串标签

热键和非自动替换热字串不再是标签; 相反, 它们(自动) 定义了一个函数. 对于多行热键, 使用大括号将热键的主体括起来, 而不是用 `return`(其是隐式的大括号的结束) 终止它. 要允许显式调用一个热键, 在 `::` 和 `{` 之间指定 `funcName(ThisHotkey)` - 这在 v1.1.20+ 中也可以做到, 但现在有一个参数. 当函数定义不是显式的时, 参数被命名为 ThisHotkey.

注意: 热键函数默认是假定-局部的, 因此在没有声明的情况下不能赋值给全局变量.

### 名称

现在, 函数和变量名被放在一个共享的命名空间中.

- 每个函数定义在当前空间内创建一个常量(只读变量).
- 使用 `MyFunc` 代替 `Func("MyFunc")`.
- 当把函数传递给任何内置函数如 SetTimer 或 Hotkey 时, 使用 `MyFunc` 代替 `"MyFunc"`. 不再支持传递名称(字符串).
- 当按值调用函数时, 使用 `myVar()` 代替 `%myVar%()`.
- 当你只有一个函数名(字符串) 时, 要调用一个函数, 首先要使用一个双解引将名称解析为一个变量并检索其值(函数对象). `%myVar%()` 现在实际上是执行了一个双解引, 然后调用结果, 等同于 `f := %myVar%, f()`. 尽可能避免按名称(字符串) 来处理函数; 使用引用来代替.

    ; 译者注: 添加一个 v2 示例来进行说明
    myvar  := f
    myvar2 := "f"
    msgbox myvar(1,2)    ; 显示结果 3
    msgbox %myvar2%(1,2) ; 显示结果 3

    f(a,b)
    {
    return a+b
    }

名称不能以数字开头, 也不能包含以前允许的下列字符: `@ # $`. 只允许使用字母, 数字, 下划线和非 ASCII 字符.

保留关键字: 声明关键字和控制流语句的名称不能作为变量, 函数或类的名称. 这包括 `local`, `global`, `static`, `if`, `else`, `loop`, `for`, `while`, `until`, `break`, `continue`, `goto`, `return`, `switch`, `case`, `try`, `catch`, `finally` 和 `throw`. 这样做的主要目的是为了检测错误, 如 `if (ex) break`.

保留关键字: `as`, `and`, `contains`, `false`, `in`, `is`, `IsSet`, `not`, `or`, `super`, `true`, `unset`. 这些词是为将来使用或其他特定目的而保留的, 即使没有歧义, 也不允许作为变量或函数名. 这主要是为了保持一致性: 在 v1 版本中, `and := 1` 被允许放在单独的行中, 但 `(and := 1)` 就不行了.

上面列出的词被允许作为属性或窗口组名称. 典型使用的属性名称前面都有 `.`, 这可以防止该词被解释为运算符. 相比之下, 关键字永远不会被解释为表达式中的变量或函数名. 例如, `not(x)` 等同于 `not (x)` 或 `(not x)`.

许多类是预定义的, 与用户定义的类的方式相同, 这有效地保留了这些全局变量名. (然而, 下面描述的域的变更缓解了由此产生的大部分问题.) 关于类的列表, 请参阅内置的类.

### 作用域

超级-全局 已被删除(不包括内置变量, 因为它们不能被重新声明或隐藏, 所以不完全一样).

在假定-局部函数中, 如果给定的名称没有在声明中使用, 也没有作为非动态赋值或引用(&) 运算符的目标, 则它可能解析为一个现有的全局变量.

换句话说:

- 函数现在可以在不声明的情况下读取全局变量.
- 没有 `global` 声明的函数不能直接修改全局变量(消除了意外副作用的一个来源).
- 在脚本中添加一个新的 `class`, 不太可能影响任何现有函数的行为, 因为类不是超全局的.
- `global` 关键字目前在全局范围内使用时是多余的, 但为了清晰起见可以使用. 这样声明的变量现在不太可能与局部变量发生冲突(比如在手动组合脚本或使用 #Include 时), 因为它们不是超全局的. 另一方面, 也失去了一些便利.
- 声明通常不需要那么多.

强制-局部 模式已被删除.

### 变量

局部 static 变量在执行到它们时被初始化, 而不是在自动执行部分开始前按线性顺序执行. 每个初始化器在第二次到达时没有效果. 多个声明是允许的, 并且可以在不同时间为同一个变量执行. 这有多种好处:

- 当静态初始化器用静态变量调用其他函数时, 由于函数定义的顺序, 初始化器尚未执行的风险较小.
- 因为函数已经被调用, 所以参数, A_ThisFunc 和闭包都是可用的(以前不可用).
- 静态变量可以被有条件地初始化, 增加了灵活性, 同时仍然只执行一次, 不需要 `if IsSet()`.
- 由于一个静态变量可能有多个初始化器, 所以允许联合赋值, 如 `static x += 1`. (这个改变稍微减少了代码的大小, 因为它已经被 `local` 和 `global` 所允许.)

注意: `static init := somefunction()` 不能再用于自动执行 somefunction. 然而, 由于现在可以完全避免基于标签和返回的子程序, 因此自动执行部分能够跨越整个脚本.

使用 `local` 声明一个变量不再使函数假定-全局.

双重引用现在与加载时解析的变量更加一致, 不再能够创建新的变量. 这避免了一些不一致的地方和常见的混淆点.

由于任何原因而失败的双重引用现在会抛出错误. 以前, 任何具有无效名称的情况都会默默地产生一个空字符串, 而其他情况会创建并返回一个空变量.

### 表达式

带引号的原义字符串可以用 `"双引号"` 或 `'单引号'` 书写, 但必须以相同的引号开始和结束. 原义引号的写法是在引号前加一个转义字符 - ``"` 或 ``'` - 或者使用相反类型的引号: `'"42" is the answer'`. 双重引号没有特殊意义, 而且会导致错误, 因为自动串联需要一个空格.

运算符 `&&`, `||`, `and` 和 `or` 产生的值取决于结果, 与 JavaScript 和 Lua 类似. 例如, `"" or "default"` 产生 `"default"` 而不是 `1`. 需要纯布尔值(0 或 1) 的脚本可以使用类似 `!!(x or y)` 或 `(x or y) ? 1 : 0` 语句.

自动连接(串联) 现在要求在所有情况下至少有一个空格或制表符(v1 版的文档说 "应该有" 一个空格).

像 `x(), y()` 这样的多语句表达式的结果是最后一个(最右边) 子表达式, 而不是第一个(最左边的) 子表达式. 在 v1 和 v2 中, 子表达式是按从左到右的顺序计算的.

逗号后的等号不再是赋值: 在 `x:=y, y=z` 中的 `y=z` 是一个无效的比较而不是赋值.

`:= += -= *= /= ++ --` 具有一致的行为, 无论它们是单独使用还是与其他运算符结合使用, 例如 `x := y, y += 2`. 以前, 当表达式中出现错误或在数学运算中使用空白值时, 会出现不同的行为.

`!=` 现在总是不区分大小写的, 就像 `=` 一样, 而 `!==` 已经被添加为 `==` 的对应词.

`<>` 已被删除.

如果给定的是一个浮点数字, `//` 现在会抛出异常. 以前, 负浮点数和负整数之间的结果是不一致的.

现在如果给定的是浮点数, `|`, `^`, `&`, `<<` 和 `>>` 现在会抛出异常, 而不是截断为整数.

科学记数法可以在没有小数点的情况下使用(但还是会产生一个浮点数). 当数字字符串被转换为整数时, 也支持科学符号(例如, "1e3" 被解释为 1000 而不是 1).

函数调用现在几乎允许任何子表达式来指定要调用的函数, 只要在参数列表的开括号前没有空格或制表符. 例如, `MyFunc()` 将调用 `MyFunc` 不管它是函数的实际名称还是包含函数对象的变量, 而 `(a?b:c)()` 将根据 `a` 调用 `b` 或 `c`. 注意 `x.y()` 仍然是一个方法调用, 大致相当于 `(x.y)(x)`, 但 `a[i]()` 现在相当于 `(a[i])()`.

双重引用现在几乎允许任何表达式 (不仅仅是变量) 作为变量名的来源. 例如, `DoNotUseArray%n+1%` 和 `%(%triple%)%` 都是有效的. 双重引用语法现在也被用来解除对 VarRefs 的引用, 例如 `ref := &var, value := %ref%`.

表达式 `funcName[""]()` 和 `funcName.()` 不再按名称调用一个函数. 省略 `.()` 中的方法名称现在会导致加载时的错误信息. 函数应该通过引用来调用或处理, 而不是通过名字.

`var :=` 没有 r-值时, 在加载时被视为错误. 在 v1 版中, 它相当于 `var := ""`, 但如果与另一个表达式结合使用, 就会静默失败 - 例如: `x :=, y :=`.

当一个原义字符串后面跟了一个不确定的一元/二元运算符时, 在加载时就会报告错误. 例如, `"new counter:" ++counter` 可能应该是递增和显示 `counter`, 但从技术上讲, 它是无效的 addition 和 unary plus.

`word ++` 和 `word --` 不再是表达式, 因为 `word` 可以是一个用户定义的函数(而且 ++/- 后面可能有一个表达式, 产生一个变量引用). 要写一个独立的后增量或后减量表达式, 要么省略变量和运算符之间的空格, 要么用括号包住变量或表达式.

`word ? x : y` 仍然是一个三元表达式, 但是以单词开头的更复杂的情况, 如 `word1 word2 ? x : y`, 总是被解释为对 word1 的函数调用(即使不存在这样的函数). 要写一个具有复杂条件的独立的三元表达式, 请将条件放在括号里.

新的 `is` 运算符, 如 `x is y` 可以用来检查值 x 是否是类 y 的实例, y 必须是一个具有 `prototype` 属性的对象(即一个类). 这包括原始值, 如 `x is Integer`(这严格来说是一种类型检查, 而 `IsInteger(x)` 检查的是潜在的转换).

关键词 `contains` 和 `in` 被保留下来供将来使用.

`&var`(取址) 已被 `StrPtr(var)` 和 `ObjPtr(obj)` 取代, 以更清楚地显示意图并加强错误检查. 在 v1 中, 取址返回 var 内部字符串缓冲的地址, 即使它包含一个数字(但不是一个对象). 它还用于检索一个对象的地址, 而得到一个错误类型的地址会产生可怕的后果.

`&var` 现在是引用运算符, 它与所有的 ByRef 和 OutputVar 参数一起使用, 以提高清晰度和灵活性(并使其他语言变化成为可能). 有关详情, 请参阅变量引用(VarRef).

字符串长度现在在表达式计算期间被缓存. 这提高了性能, 并允许字符串包含二进制零. 特别是:

- 两个字符串的串联, 其中一个或两个都包含二进制零, 不再导致数据的截断.
- 区分大小写的等号运算符(`==` 和 `!==`) 可以用来比较二进制数据. 其他比较运算符只能 "看到" 第一个二进制零.
- 二进制数据可以从函数返回并分配给对象.

大多数函数仍然期待空终止字符串, 所以只能 "看到" 第一个二进制零. 例如, MsgBox 将只显示第一个二进制 0 之前的字符串部分.

`*`(deref) 运算符已经被删除. 请使用 NumGet 代替.

`~`(bitwise-NOT) 运算符现在总是将其输入作为 64 位有符号整数处理; 它不再将 0 到 4294967295 之间的值作为无符号 32 位处理.

新增 `>>>` 和 `>>>=` 用于逻辑右位移.

新增 胖箭头函数. 表达式 `Fn(Parameters) => Expression` 定义了一个名为 Fn 的函数(可以是空白), 并返回一个 Func 或闭包对象. 当调用时, 该函数计算 Expression 并返回结果. 当在另一个函数中使用时, Expression 可以引用外层函数的变量(这也可以通过普通的函数定义来实现).

胖箭头语法也可以用来定义方法和属性的获取器/设置器(在这种情况下, 方法/属性定义本身并不是一个表达式, 但其主体只是返回一个表达式).

在成员访问的左侧, 现在完全支持原义数字(点). 例如, `0.1` 是一个数字, 但 `0.min` 和 `0.1.min` 访问的是 min 属性, 这可以由一个基对象来处理(请参阅原始值). `1..2` 或 `1.0.2` 是数字 1.0, 后面是属性 2. 使用实例可能是实现测量单位, 原义版本号或范围.

`x**y`: 当 `x` 和 `y` 时整数, 并且 `y` 是正数, 如果在范围内, 幂运算符现在对所有输入都给出正确的结果, 以前由于内部使用浮点数, 一些精度会丢失. 溢出的行为未被定义.

### 对象(杂项)

另请参阅: 对象

现在, 使用 `.` 访问属性和使用 `[]` 访问数据(项, 数组和 Map 元素) 之间有了区别. 例如, `dictionary["Count"]` 可以返回 "Count" 的定义, 而 `dictionary.Count` 返回其中包含的单词数. 用户定义的对象可以通过定义 __Item 属性来利用这一点.

如果事先不知道属性或方法的名称, 可以(而且必须) 用百分号来访问它. 例如, `obj.%varname%()` 是 `obj[varname]()` 的 v2 等价物. `[]` 的使用是为数据(如数组元素) 保留的.

构造专用对象的原义语法基本上仍然是 `{name: value}`, 但由于普通对象现在只有 "属性" 而不是 "数组元素", 为了与在其他情况下访问属性的方式保持一致, 规则略有改变:

- `o := {a: b}` 和以前一样使用名称 "a".
- `o := {%a%: b}` 使用 `a` 的值作为属性名而不是将其作为一个变量名, 执行双重解引并使用结果变量的内容. 换句话说, 它与 `o := {}, o.%a% := b` 的效果相同.

    ; 译者注: 添加一个 v2 示例来进行说明
    Var  := "abc"
    Val  := 123
    Obj  := {%Var%: Val}
    Obj2 := {Var: Val}
    Msgbox  Obj.abc  ; 显示结果 123
    Msgbox Obj2.Var  ; 显示结果 123

- 在 `:` 左边的任何其他类型的表达式都是非法的. 例如, `{(a): b}` 或 `{an error: 1}`.

`base.Method()` 中的单词 "base" 的使用被替换为 super(`super.Method()`), 以更好地区分这两个概念:

- `super.` 或 `super[` 调用方法/属性的超类版本, 其中 "super-class" 是最初与当前函数定义相关联的原型对象的基.
- `super` 是一个保留关键字; 试图在没有 `.` 或 `[` 或 `(` 后缀的情况下使用它, 或者在一个类之外使用, 会导致加载时错误.
- `base` 是一个预定义的属性, 用于获取或设置对象的直接基对象(如 ObjGetBase/ObjSetBase). 它只是一个普通的属性名, 而不是保留的.
- 当超类没有定义 x 时, 调用`super.x` 会产生一个错误, 而以前 `base.x` 是被忽略的(即使它是一个赋值).

当 Fn 是一个对象时, `Fn()`(以前写成 `%Fn%()`) 现在调用 `Fn.Call()` 而不是 `Fn.()`(现在只能写成 `Fn.%""%()`). 函数不再支持没有名称的方法.

`this.Method()` 调用 `Fn.Call(this)`(其中 Fn 是实现该方法的函数对象) 而不是 `Fn[this]()`(在 v1 中, 这将导致调用 `Fn.__Call(this)` 除非 `Fn[this]` 包含一个函数). 函数对象应该实现一个 Call 方法, 而不是 __Call, 后者只用于显式方法调用.

如果定义了 `__New` 方法但无法调用它(例如因为参数计数不正确), 或者如果传递了参数但没有定义 `__New`, `Classname()`(以前的 `new Classname()`) 现在不能创建对象.

在表达式中创建的对象或从函数返回的对象现在被保留到表达式计算完成, 然后释放. 这稍微提高了性能, 并允许在表达式中使用临时对象进行内存管理, 而不必担心对象被过早释放.

对象可以包含包含二进制零的字符串值(但不是键). 克隆对象可以保留字符串中的二进制数据, 最多可以保留字符串的存储长度(而不是其容量). 历史上, 在处理二进制数据或结构时, 写入的数据超过值的长度; 现在, 应该使用 Buffer 对象来代替.

诸如 `x.y := z` 这样的赋值表达式现在总是产生 `z` 的值, 不管 `x.y` 是如何实现的. 属性设置器的返回值现在被忽略了. 以前是这样:

- 一些内置对象返回 `z`, 一些返回 `x.y`(如 `c := GuiObj.BackColor := "red"` 设置 `c` 为 `0xFF0000`), 还有一些返回不正确的值.
- 用户定义的属性设置器可能返回了意外值或无法返回任何内容.

`x.y(z) := v` 现在是一个语法错误. 此前, 它等同于 `x.y[z] := v`. 一般来说, `x.y(z)`(方法调用) 和 `x.y[z]`(参数化属性) 是两个不同的操作, 尽管如果 `x` 是一个 COM 对象, 它们可能是等同的(由于 COM 接口的限制).

将一个对象与另一个值串联或传递给 Loop, 目前被视为一个错误, 而以前该对象被视为一个空字符串. 这可能会更改为隐式调用 `.ToString()`. 使用 `String(x)` 将一个值转换为一个字符串; 如果 `x` 是一个对象, 这将调用 `.ToString()`.

当一个对象通过 IDispatch(COM 接口) 被调用时, 任何不能被传回给调用者的未捕获的异常将导致错误对话框. (调用者可能会也可能不会在没有任何具体细节的情况下显示额外错误对话框). 这也适用于由于使用 ComObjConnect 而被调用的事件处理程序.

### 函数

函数不能再以超过其正式接受的参数来动态调用.

可变函数不受上述限制的影响, 但通常会在每次调用时创建一个数组来保存多余的参数. 如果不需要这个数组, 现在可以省略参数名以防止它被创建:

    AcceptsOneOrMoreArgs(first, *) {
      ...
    }

这可以用于不需要额外参数的回调.

可变函数调用现在允许任何可枚举的对象, 而以前它们需要一个具有连续数字键的标准对象. 如果枚举器每次迭代返回一个以上的值, 只使用第一个值. 例如, `Array(mymap*)` 创建一个包含 mymap 键的数组.

可变函数调用以前对命名参数的支持不完整. 这已经被禁用, 以消除正确实现命名参数的可能障碍.

用户定义的函数可以使用新的关键字 `unset` 作为参数的默认值, 以使参数在没有提供值的情况下被 "unset". 然后函数可以使用 IsSet() 来确定是否提供了一个值. `unset` 目前不允许在任何其他情况下使用.

当函数调用出现而没有定义时, 脚本不再从函数库(Lib) 文件夹中自动包含, 这是因为复杂性和潜在的事故增加(现在, `MyFunc()` 中的 `MyFunc` 可以是任何变量). `#Include <LibName>` 和以前一样工作. 在未来的版本中, 它可能会被模块支持所取代.

可变内置函数现在的 `MaxParams` 值等于 `MinParams`, 而不是一个任意的数字(如 255 或 10000). 使用 `IsVariadic` 来检测, 没有上限.

#### ByRef

ByRef 参数现在使用 `&param`, 而不是 `ByRef 参数` 来声明, 在用法上有一些区别.

ByRef 参数不再隐式地接受对调用者的变量的引用. 相反, 调用者必须用引用运算符(`&var`) 显式地传递一个引用. 这允许更多的灵活性, 例如在其他地方存储引用, 用可变函数接受引用, 用可变调用传递引用.

当一个参数被标记为 ByRef 时, 任何试图显式传递非 VarRef 值的行为都会导致抛出错误. 否则, 函数可以用 `param is VarRef` 检查引用, 用 `IsSetRef(param)` 检查目标变量是否有一个值, 并用 `%param%` 显式解除引用.

在递归调用时, ByRef 参数现在能够从同一函数的前一个实例中接收对局部变量的引用.

### 套嵌函数

一个函数可以被定义在另一个函数里面. 一个嵌套的函数可以自动从包围的函数中 "捕获" 非静态局部变量(在适当的条件下), 允许它们在包围的函数返回后使用.

新的 "胖箭头" `=>` 运算符也可以用来创建嵌套函数.

有关详情, 请参阅套嵌函数.

### 未分类

当初始化一个已声明的变量或可选参数时, 必须用 `:=` 来代替 `=`.

`return %var%` 现在做了一个双重引用; 以前它等同于 `return var`.

#Include 默认是相对于包含当前文件的目录而言的. 其参数现在可以选择用引号括起来.

#ErrorStdOut 的参数现在可以选择用引号括起来.

现在要求标签名称只能由字母, 数字, 下划线和非 ASCII 字符组成(与变量, 函数等相同).

函数中定义的标签具有局部空间(作用域); 它们只在该函数中可见, 并且不会与其他地方定义的标签冲突. 局部标签不可能被外部调用(即使通过内置函数). 可以使用嵌套函数来代替, 允许充分使用局部变量.

`for k, v in obj`:

- 对象的调用方式已经改变. 请参阅 枚举器.
- 在循环中断或完成后, `for` 将 k 和 v 恢复到循环开始前的值.
- 如果 obj 不是对象, 或者在检索或调用其枚举器时出现问题, 则抛出异常.
- 最多可以使用 19 个变量.
- 变量可以被省略.

转义逗号不再有任何意义. 以前, 如果在命令参数中没有括号的表达式中使用, 就会强制逗号被解释为多语句运算符, 而不是作为参数之间的分隔符. 它只对命令起作用, 不能用于函数或变量声明.

转义序列 ``s` 现在可以在支持 ``t` 的地方使用. 以前只允许 #IfWin 和 (Join.

`*/` 现在可以放在行尾, 以结束多行注释, 以解决 `/* */` 在其他语言中工作方式有关的一个常见的混淆点. 由于存在歧义的风险(例如以 `*/` 结尾的热字符串), 任何没有以 `*/` 结尾的 `/*` 不再被忽略(颠覆了(逆转) AHK_L 修订版 54 中的更改).

在支持的范围(64 位有符号整数) 之外的整数常量和数字字符串现在会溢出/环绕, 而不是被限制在最小/最大值上. 这与数学运算符一致, 因此 `9223372036854775807+1 == 9223372036854775808`(但两者都生成 -9223372036854775808). 这有利于对 64 位数值进行位操作.

对于数字字符串来说, 除了空格和制表符之外, 允许在数字前面添加空白字符的情况比较少. 一般的规则(在 v1 和 v2 中) 是只允许使用空格和制表符, 但在某些情况下, 由于 C 语言运行库的惯例, 允许使用其他空白字符.

`Else` 现在可以与 `Loop`, `For`, `While` 和 `Catch` 一起使用. 对于循环, 如果循环的迭代次数为零, 它将被执行. 对于 `Catch`, 如果在 `Try` 中没有抛出异常它将被执行(如果抛出任何错误或值, 即使没有与值的类别相匹配的 `Catch` 也不会被执行). 因此, 在不使用大括号的情况下 `Else` 的解释可能与以前的版本不同. 例如:

    if condition
    {
        while condition
            ; 每个迭代都会执行的语句
    } ; 这些大括号现在是必须的, 否则 else 会与 while 相关联
    else
        ; if condition 为 false 时, 执行的语句

### 延续片段

智能 LTrim: 默认行为是计算延续片段选项下面第一行的前导空格或制表符的数量, 并从此后的每一行中删除该数量的空格或制表符. 如果第一行中混合了空格和制表符, 只有第一种类型的字符被视为缩进. 如果任何一行的缩进幅度小于第一行, 或者使用了错误的字符, 则该行的所有前导空白将被原封不动地保留.

如果延续片段以引号字符串开始, 引号会被自动转义(即它们被解释为原义字符). 这就避免需要在多行字符串中转义引号(如果开始和结束的引号都在延续片段之外), 同时仍然允许多行表达式包含带引号字符串.

如果延续片段上方的行以一个名称字符结束, 并且该部分不以引号字符串开始, 则会自动插入一个空格, 以分隔名称和延续片段的内容. 这允许在 `return`, 函数调用语句等之后的多行表达式中使用一个延续片段. 它还可以确保变量名称不与其他标记(或名称) 连接, 造成无效的表达.

表达式中的换行字符(``n`) 被当作空格处理. 这使得多行表达式可以使用默认选项(即省略 `Join`) 的延续片段来编写.

由于不再需要转义这些字符, 所以删除 `,` 和 `%` 选项.

如果 `(` 或 `)` 出现在延续片段潜在的选项中(除了作为 `Join` 选项的一部分), 整个行不会被解释为延续片段的开始. 换句话说, 像 `(x.y)()` 和 `(x=y) && z()` 这样的行会被解释为表达式. 多行表达式也可以在行首以开括号开始, 前提是在第一个物理行中至少有一个其他的 `(` 或 `)`. 例如, 整个表达式可以用 `((` ... `))` 括起来.

不包括上述情况, 如果存在任何无效的选项, 会显示加载时错误, 而不是忽略无效的选项.

以 `(` 开头的行和以 `:` 结尾的行不再因为看起来像一个标签而被排除为延续片段的开头, 因为 `(` 在标签名称中不再有效. 这使得像 `(Join:` 这样的代码有可能是一个延续片段的开始. 然而, `(:` 是一个错误, 而 `(::` 仍然是一个热键.

在表达式和函数/属性定义中支持一种新的延续行方法, 它利用了每个 `(`/`[`/`{` 必须与相应的 `)`/`]`/`}` 相匹配这一事实. 换句话说, 如果一行包含一个未封闭的 `(`/`[`/`{` 它将与后续的行连接, 直到开头和结尾符号的数量平衡. 如果没有其他未封闭的符号, 并且括号前面没有紧接着的运算符, 那么行末的括号 `{` 被认为是一个真正的括号(而不是一个对象原义语法的开始).

### 延续行

现在, 对于将符号视为表达式运算符的上下文, 行延续更具选择性. 一般来说, 逗号和表达式运算符不能再用于文本上下文中的延续, 例如与热字符串或指令(除 #HotIf 外) 一起, 或在未封闭的引号字符串之后.

行延续现在对行末的表达式运算符有效.

`is`, `in` 和 `contains` 可以用于行延续, 尽管 `in` 和 `contains` 仍然被保留/尚未作为运算符实现.

`and`, `or`, `is`, `in` 和 `contains` 可以作为行延续运算符, 即使后面是赋值或其他二进制运算符, 因为这些不再是有效的变量名. 相比之下, v1 版对 `and`/`or` 后跟以下任一项都有例外: `<>=/|^:,`

当 `.` 被用于延续时, 如果在一行开始时 `.` 右边没有空格或制表符, 那么这两行就不再自动用空格分隔, 如 `.VeryLongNestedClassName`. 请注意 `x .123` 总是属性访问(而不是自动串联), 而 `x+.123` 在有或没有空格的情况下都可以工作.

### 类型

一般来说, v2 对任何依赖值的类型的代码产生更一致的结果.

在 v1 中, 一个变量可以同时包含一个字符串和一个缓存的二进制数字, 每当变量被用作数字时, 这个数字就会被更新. 由于这个缓存的二进制数是检测数值类型的唯一方法, 由表达式(如 `var+1` 或 `abs(var)`) 进行的内部缓存的副作用有效地改变了 `var` 的 "类型". v2 禁用了这种缓存, 因此 `str := "123"` 总是一个字符串 `int := 123` 总是一个整数. 因此, `str` 每次被用作数字时都需要进行转换(而不是只在第一次), 除非它最初被赋值为纯数字.

内置的 "变量" `true`, `false`, `A_PtrSize`, `A_IsUnicode`, `A_Index` 和 `A_EventInfo` 总是返回纯整数, 而不是字符串. 在 v1 中, 由于某些优化, 它们有时会返回字符串, 而这些优化在 v2 中已经被取代了.

所有的原义数字在加载时都被转换为纯二进制数字, 而它们的字符串表示形式则被丢弃. 例如, `MsgBox 0x1` 等同于 `MsgBox 1`, 而 `MsgBox 1.0000` 等同于 `MsgBox 1.0`(因为浮点数的格式已经改变). 在变量中存储数字或从用户自定义函数中返回数字都会保留其纯数字状态.

现在浮点数的默认格式指定为 `.17g` (以前是 `0.6f`), 这在很多情况下更紧凑, 更准确. 默认值不能更改, 但可以用 `Format` 来获得不同的格式.

带引号的字符串和通过连接带引号的字符串产生的字符串不再无条件地被视为非数字. 相反, 它们被视为与存储在变量中或从函数中返回的字符串相同. 这有以下影响:

- 带引号的文字 `"0"` 被认为是 false.
- `("0xA") + 1` 和 `("0x" Chr(65)) + 1` 产生 11 而不是失败.
- `x[y:="0"]` 和 `x["0"]` 现在的行为是一样的.

如果运算数都是字符串, 运算符 `=` 和 `!=` 现在按字母顺序比较它们的运算数, 即使它们是数字字符串. 当两个运算数都是数字且至少有一个运算数是纯数字(不是字符串) 时, 仍然进行数字比较. 例如, 54 和 "530" 是数字比较, 而 "54" 和 "530" 是字母比较. 此外, 存储在变量中的字符串与原义字符串的处理没有区别.

关系运算符 `<`, `<=`, `>` 和 `>=` 如果与非数字字符串一起使用, 现在会出现异常. 以前, 它们根据两个输入是否为数字, 按字母顺序进行比较, 但带引号的字符串总是被认为是非数字的. 使用 `StrCompare(a, b, CaseSense)` 代替.

`Type(Value)` 返回下列字符串之一: 字符串, 整数, 浮点数, 或者一个对象的具体类.

`Float(v)`, `Integer(v)` 和 `String(v)` 将 `v` 转换成相应的类型, 如果不能进行转换, 则抛出异常(例如 `Integer("1z")`). `Number(v)` 转换为整数或浮点数. 如果 `v` 时一个对象, `String` 调用 `v.ToString()`. (在理想情况下, 这适用于任何从对象到字符串的隐式转换, 但目前的实现使其变得困难.)

## 对象

对象现在使用更结构化的类-原型方法, 将类/静态成员与实例成员分开. 许多内置方法和 Obj函数已经被移动, 重命名, 更改或删除.

- 每个用户定义的或内置的类都是一个类对象(Class 的实例) 只公开使用 `static` 关键字定义的方法和属性(包括从基类继承的静态成员) 和嵌套类.
- 每个类对象都有一个 Prototype 属性, 成为该类所有实例的 `base`. 类体内的所有非静态方法和属性定义都附着在原型对象上.
- 实例化是通过调用静态方法 Call 来执行的, 如 `myClass.Call()` 或 `myClass()`. 这允许类完全覆盖构造行为(例如, 实现类工厂或单件, 或者构造一个原生数组或 Map 而不是一个对象), 尽管初始化通常仍应在 `__New` 中执行. 现在 `__New` 的返回值被忽略; 要覆盖返回值, 请在 Call 方法中这样做.

混合的对象类型已经被拆分成 `Object`, `Array` 和 `Map`(关联数组).

Object 现在是所有用户定义 和内置 对象的根类(这不包括 VarRef 和 COM 对象). 添加到 `Object.Prototype` 的成员被所有 AutoHotkey 对象继承.

运算符 `is` 的期望是一个类, 所以 `x is y` 在基对象链中检查 `y.Prototype`. 要检查 `y` 本身, 请调用 `x.HasBase(y)` 或 `HasBase(x, y)`.

用户定义的类也可以显式地继承 `Object`, `Array`, `Map` 或其他一些内置类(尽管这样做并不总是有用), 如果没有指定, 那么 `Object` 就是默认的基类.

`new` 运算符已被删除. 取而代之的是省略该运算符, 如 `MyClass()`. 要 基于 一个不是类的对象的构造一个对象, 请使用 `{}` 或 `Object()`(或通过任何其它方法) 创建该对象, 并设置它的 `base`. `__Init` 和 `__New` 可以被显式调用, 但一般来说, 这仅适用于实例化类.

嵌套类的定义现在产生一个带有 get 和 call 访问函数的动态属性, 而不是一个简单的值属性. 这是为了支持以下行为:

- `Nested.Class()` 不会将 `Nested` 传递给 `Nested.Class.Call` 并最终传递给 `__New`, 否则会发生这种情况, 因为这是作为方法调用的函数对象的正常行为(这是这里使用嵌套类的方式).
- `Nested.Class := 1` 默认是一个错误(该属性是只读的).
- 第一次引用或调用该类会使其初始化.

GetCapacity 和 SetCapacity 被删除.

- ObjGetCapacity 和 ObjSetCapacity 现在只影响对象包含属性的容量, 而且预计不会被经常使用. 不支持设置属性, 数组元素或 Map 元素的字符串缓冲的容量; 对于二进制数据, 使用缓冲对象.
- Array 和 Map 具有 Capacity 属性, 对应于对象的当前数组或 Map 分配的容量属性.

其他多余的 Obj函数(对应于 Object 的内置方法) 被删除. ObjHasOwnProp(以前的 ObjHasKey) 和 ObjOwnProps(以前的 ObjNewEnum) 被保留下来, 以便于安全检查重新定义了这些方法的对象(以及没有定义这些方法的原始原型). ObjCount 被 ObjOwnPropCount 取代(仅是一个函数, 适用于所有对象), Map 具有自己的 Count 属性.

ObjRawGet 和 ObjRawSet 合并为 GetOwnPropDesc 和 DefineProp. 添加它们的最初原因被其他变化所取代, 例如 `Map` 类型, 元函数工作方式的变化以及 DefineProp 本身在某些方面取代了元函数.

顶级类的定义现在创建了一个常量(只读变量); 也就是说, 现在对类名的赋值是一个错误, 而不是一个可选的警告, 除非是局部变量遮蔽了全局类(现在在函数内部赋值时默认发生).

### 原始值

原始值通过将方法和属性调用委托给基于其类型的原型对象来模拟对象, 而不是 V1 的 "默认基对象". Integer 和 Float 继承于 Number. String 和 Number 继承于 Primitive. Primitive 和 Object 继承于 Any. 这些都作为预定义的类存在.

### 属性和方法

与 v2.0-a104 到 v2.0-a127 不同, 方法是由属性定义的, 方法与属性是分开的. 然而, 与 v1 不同的是, 由类方法定义(或内置方法) 创建的属性默认为只读. 方法仍然可以通过分配新的值属性来创建, 这通常与 v1 中的作用相同.

Object 类定义了处理属性和方法的新方法: DefineProp, DeleteProp, GetOwnPropDesc, HasOwnProp, OwnProps. 为所有的值定义了额外的方法(除了 ComObjects): GetMethod, HasProp, HasMethod.

Object, Array 和 Map 现在是独立的类型, 数组元素与属性是分开的.

所有内置方法和属性(包括 `base`) 的定义方式与用户定义的相同. 这确保了行为的一致性, 并允许检测, 检索或重新定义内置和用户定义的成员.

如果属性不接受参数, 它们会自动传递给由该属性返回的对象(或者抛出).

试图检索一个不存在的属性对于所有类型的值或对象来说都被视为一个错误, 除非 `__get` 被定义. 然而, 在大多数情况下, 设置一个不存在的属性将创建它.

多维数组技巧被删除. `x.y[z]:=1` 不再在 `x.y` 中创建一个对象, 并且 `x[y,z]` 是一个错误, 除非 x.__item 处理两个参数(或者 x.__item.__item 处理, 等等.).

如果一个属性定义了 `get` 但没有 `set`, 赋值会抛出而不是覆盖该属性.

DefineProp 可以用来定义检索, 设置 或调用 特定的属性时会发生什么, 而不需要定义任何元函数. 类中的属性和方法定义使用相同的机制, 因此可以用相同的名称定义属性 getter/setter(获取器/设置器) 和方法.

`{}` 对象原义语法现在直接设置 自有属性 的值或对象的 `base`. 也就是说, `__Set` 和属性设置器不再被调用(通常只有在参数列表中设置了 `base` 时才有可能).

### Static/Class 变量

Static/class 初始化器现在在 `static __Init` 方法的上下文中执行, 所以 `this` 指的是类, 初始化器可以创建局部变量. 它们在类第一次被引用时被计算(而不是在自动执行部分开始前被计算, 严格按照定义的顺序). 如果类没有被更早地引用, 它们会在执行过程中到达类定义时被计算, 所以全局变量的初始化可以先发生, 而不用把它们放到类中.

### 元函数

元函数被大大简化了; 它们的行为就像普通方法一样:

- 它们在层次结构中被定义的位置并不重要.
- 如果被覆盖, 基本版本不会被自动调用. 如果需要, 脚本可以调用 `super.__xxx()`.
- 如果定义了, 它必须执行默认的动作; 例如, 如果 __set 不存储值, 它就不被存储.
- 行为不依赖于方法是否使用 `return` (但当然, __get 和 __call 仍然需要返回一个值).

方法和属性参数是以数组形式传递的. 这对链式基类/超类的调用进行了优化, 并且(与 MaxParams 验证相结合) 鼓励作者处理 args. 对于__set, 被分配的值被单独传递.

    this.__call(name, args)
    this.__get(name, args)
    this.__set(name, args, value)

定义的属性和方法优先于元函数, 不管它们是否被定义在基对象中.

__Call 不会在内部调用 __Enum(以前的 _NewEnum) 或 Call 时被调用, 例如当一个对象被传递到 for-loop 或一个函数对象被 SetTimer 调用时.

当每个类被初始化时, 静态方法 __New 被调用, 如果由该类定义或从超类继承. 有关详情, 请参阅 Static/Class 变量(上面的) 和 Class 初始化.

### Array

`class Array extends Object`

数组对象包含值的列表或序列, 第一个元素的索引为 1.

当分配或检索一个数组元素时, 索引的绝对值必须在 1 和数组的 Length 之间, 否则会产生一个异常. 一个数组可以通过适当的方法插入或删除元素, 或者通过分配 Length 来调整大小.

目前在访问元素时需要使用括号, 即 `a.1` 指的是一个属性, 而 `a[1]` 指的是一个元素.

负值可以用来做反向索引.

Clone, Delete, InsertAt, Pop, Push 和 RemoveAt 的用法基本没有变化. HasKey 被重命名为 Has. Length 现在是一个属性. 增加 Capacity 属性.

数组可以用 `Array(values*)` 或 `[values*]` 构建. 可变函数接收一个数组的参数, 数组也由几个内置函数创建.

For-loop 的用法是 `for val in arr` 或 `for idx, val in arr`, 其中 `idx = A_Index` 是默认的. 也就是说, 缺乏值的元素仍然被枚举, 如果只传递一个变量, 则不返回索引.

### Map

Map 对象是一个关联数组, 其功能类似于 v1 的对象, 但没有那么多歧义.

- Clone 的使用方法和以前一样.
- Delete 一次只能删除一个键.
- HasKey 被改名为 Has.
- Count 现在是一个属性.
- 新属性: Capacity, CaseSense
- 新方法: Get, Set, Clear
- 字符串键默认是区分大小写的, 并且不会被转换为整数.

目前 Float 键仍然被转换为字符串.

在访问元素时, 需要使用括号; 例如 `a.b` 指的是一个属性, 而 `a["b"]` 指的是一个元素. 与 v1 不同, 一个属性或方法不能通过分配一个数组元素而被意外地禁用.

如果试图检索一个不存在的元素的值, 就会抛出异常, 除非地图定义了一个 Default 属性. `MapObj.Get(key, default)` 可以用来为每个请求显式地提供一个默认值.

使用 `Map(Key, Value, ...)` 从一个键值对列表中创建一个 Map.

### 枚举器

枚举器模型的变更:

- __Enum(n) 替换了 _NewEnum().
- 所需的参数 n 包含 for-loop 中的变量数量, 以允许它影响枚举, 而不必将初始化推迟到第一个迭代调用.
- Call() 替换了 Next(), 除了 ByRef 的工作方式不同外, 用法相同; 例如, 定义为 `Call(&a)` 的方法应该分配 `a := next_value` 而 `Call(a)` 将接收一个 `VarRef`, 所以应该分配 `%a% := next_value`.
- 如果 __Enum 不存在, 则假定该对象是一个枚举器. 这允许直接使用函数对象(如闭包).

由于数组元素和属性现在是分开的, 所以枚举属性需要通过调用 OwnProps 显式地创建一个枚举器.

### 绑定函数

当绑定函数被调用时, 由调用者传递的参数将填补在创建绑定函数时被省略的位置. 例如, `F.Bind(,b).Call(a,c)` 调用 `F(a,b,c)`, 而不是 `F(,b,a,c)`.

### COM 对象(ComObject)

COM 封装对象现在可以识别为几个不同类的实例, 这取决于它们的变体类型(和以前一样, 这影响它们支持哪些方法和属性) :

- `ComValue` 是所有 COM 包装器对象的基类.
- `ComObject` 是 VT_DISPATCH 的非空指针; 也就是说, 通常是一个有效的 COM 对象, 可以由脚本使用正常的对象语法来调用.
- `ComObjArray` 用于 VT_ARRAY(SafeArrays).
- `ComValueRef` 用于 VT_BYREF.

这些类可以用 `obj is ComObject` 和类似的手段来进行类型检查. 通过修改各自的原型对象, 可以为 ComValue, ComObjArray 和 ComValueRef(但不是 ComObject) 类型的对象定义属性和方法.

`ComObject(CLSID)` 创建 ComObject; 也就是说, 这是新的 ComObjCreate.

注意: 如果你正在更新旧的代码, 并且由于向 ComObject 传递一个整数而得到一个 TypeError, 你很可能应该调用 ComValue 来代替.

`ComValue(vt, value)` 创建一个封装对象. 它可以返回上面列出的任何类的实例. 这取代了 `ComObjParameter(vt, value)`, `ComObject(vt, value)` 以及作为参数与 变体类型 和 value 一起使用的任何其他名称. value 被转换为适当的类型(按照 COM 惯例), 而不是要求一个具有正确二进制值的整数. 特别是, 当传递一个整数时, 以下的行为与以前不同. R4, R8, Cy, Date. 指针类型像以前一样允许一个纯整数地址, 或者一个对象/ComValue.

`ComObjFromPtr(pdsp)` 是一个类似于 `ComObjEnwrap(dsp)` 的函数, 但与 ObjFromPtr 一样, 它不调用指针上的 AddRef. 在 v1 中的等价物是 `ComObject(9, dsp, 1)`; 在 v1 中省略第三个参数会引起 AddRef.

对于 ComValue 和 ComObjFromPtr, 请注意 AddRef 永远不会被自动调用; 在这方面, 它们的行为与 v1 中的 `ComObject(9, value, 1)` 或 `ComObject(13, value, 1)` 类似. 这并不一定意味着你在更新旧的脚本时应该添加 `ObjAddRef(value)` 因为许多脚本错误地使用了旧函数.

具有变体类型 VT_BYREF, VT_ARRAY 或 VT_UNKNOWN 的 COM 封装对象现在有一个 `Ptr` 属性, 相当于 `ComObjValue(ComObj)`. 这允许它们以 `Ptr` 参数类型被传递给 DllCall 或 ComCall. 它还允许将对象直接传递给 NumPut 或 NumGet, 这可以与 VT_BYREF(访问调用者的类型变量), VT_ARRAY(访问 SAFEARRAY 字段) 或 VT_UNKNOWN(检索 vtable 指针) 一起使用.

具有变体类型 VT_DISPATCH 或 VT_UNKNOWN 和空接口指针的 COM 封装对象现在有一个 `Ptr` 属性, 可以被读取或分配. 一旦分配了一个非空的指针, 该属性就是只读的. 这是为了与 DllCall 和 ComCall 一起使用, 所以在函数返回后不需要手动包装指针.

ComObjArray 的枚举现在与 Array 一致; 例如 `for value in arr` 或 `for index, value in arr`, 而不是 `for value, vartype in arr`. `index` 索引的起始值是 ComObjArray(`arr.MinIndex()`) 的下限, 通常是 0.

整数类型 I1, I8, UI1, UI2, UI4 和 UI8 现在被转换为整数, 而不是字符串. 这些在 COM 调用中很少发生, 但这也适用于 VT_BYREF 包装器. VT_ERROR 不再被转换为整数, 而是产生一个 ComValue.

### 默认属性

COM 对象可能有一个 "默认属性", 它有两种用途:

- 对象的 value, 例如, 在 VBScript 中, `MsgBox obj` 通过调用其默认成员来计算该对象.
- 一个集合的索引属性, 通常被命名为 `Item` 或 `item`.

AutoHotkey v1 没有默认属性的概念, 所以如果省略了属性名称, COM 对象包装器会调用默认属性; 即 `obj[]` 或 `obj[,x]`.

然而, AutoHotkey v2 将属性与数组/ Map /集合项分开, 为此 `obj[x]` 被映射到对象的默认属性(无论 `x` 是否存在). 对于 AutoHotkey 对象, 这就是 `__Item`.

一些代表数组或集合的 COM 对象不公开默认属性, 所以在 v2 中不能用 `[]` 来访问项目. 例如, JavaScript 数组对象和其他一些通常与 JavaScript 一起使用的对象将数组元素作为属性公开. 在这种情况下, `arr.%i%` 可以用来访问一个数组元素的属性.

当 AutoHotkey v2 的 `Array` 对象被传递给 JavaScript 时, 它的元素不能被 JavaScript 的 `arr[i]` 检索, 因为这将试图访问一个属性.

### COM 调用

通过 IDispatch 接口对 AutoHotkey 对象的调用现在透明地支持 `VT_BYREF` 参数. 这最常被用于 COM 事件(ComObjConnect).

对于 `VT_BYREF` 参数, 一个未命名的临时 var 被创建, 其值从调用者的变量中复制, 并且一个 `VarRef` 被传递给 AutoHotkey 函数/方法. 返回时, 值从临时变量中复制到调用者的变量中.

一个函数/方法可以通过声明参数 ByRef(用 `&`) 或显式解引来赋值.

例如, `VT_BYREF|VT_BOOL` 类型的参数以前会收到一个 ComObjRef 对象, 并且会被赋值, 如 `pbCancel[] := true` 或 `NumPut(-1, ComObjValue(pbCancel), "short")`. 现在, 这个参数可以被定义为 `&bCancel` 并像 `bCancel := true` 那样赋值; 或者可以被定义为 `pbCancel` 并像 `%pbCancel% := true` 那样赋值.

## 库

删除的:

- Asc() (使用 Ord)
- AutoTrim (使用 Trim)
- ComObjMissing() (改为连续写两个逗号)
- ComObjUnwrap() (如果需要, 使用 ComObjValue 和 ObjAddRef)
- ComObjEnwrap() (如果需要, 使用 ComObjFromPtr 和 ObjAddRef 代替)
- ComObjError()
- ComObjXXX() 其中 XXX 是显式定义的 ComObj 函数以外的任何东西(使用 ComObjActive, ComValue 或 ComObjFromPtr 代替).
- ControlSendRaw (使用 `ControlSend "{Raw}"` 或 ControlSendText 代替)
- EnvDiv
- EnvMult
- EnvUpdate (它的用处非常有限, 可以用一个简单的 SendMessage 代替)
- Exception (使用 Error 或一个适当的子类)
- FileReadLine (使用文件读取循环或 FileOpen)
- Func (使用像 `MyFunc` 这样的直接引用)
- Gosub
- Gui, GuiControl, GuiControlGet (请参阅 Gui)
- IfEqual
- IfExist
- IfGreater
- IfGreaterOrEqual
- IfInString
- IfLess
- IfLessOrEqual
- IfMsgBox (MsgBox 现在返回按钮名称)
- IfNotEqual
- IfNotExist
- IfNotInString
- IfWinActive
- IfWinExist
- IfWinNotActive
- IfWinNotExist
- If between/is/in/contains (但请参阅 isXXX)
- Input (使用 InputHook)
- IsFunc
- Menu (使用 Menu/MenuBar 类, TraySetIcon, A_IconTip, A_IconHidden 和 A_AllowMainWindow)
- MenuGetHandle (使用 Menu.Handle)
- MenuGetName (没有菜单名称; MenuFromHandle 是最接近的替代方法)
- Progress (使用 Gui)
- SendRaw (使用 `Send "{Raw}"` 或 SendText 代替)
- SetBatchLines (-1 现在是默认行为)
- SetEnv
- SetFormat (Format 可以用来格式化一个字符串)
- SoundGetWaveVolume/SoundSetWaveVolume (在平衡方面与 SoundGet/SoundSet 的行为略有不同, 但都没有保留平衡)
- SplashImage (使用 Gui)
- SplashTextOn/Off (使用 Gui)
- StringCaseSense (使用各种参数)
- StringGetPos (使用 InStr)
- StringLeft
  StringLen
  StringMid
  StringRight
  StringTrimLeft
  StringTrimRight -- 使用 SubStr 来代替这些命令.
- StringReplace (使用 StrReplace 代替)
- StringSplit (使用 StrSplit 代替)
- Transform
- VarSetCapacity (对二进制数据/结构使用 Buffer 对象, 对 UTF-16 字符串使用 VarSetStrCapacity)
- WinGetActiveStats
- WinGetActiveTitle
- #CommentFlag
- #Delimiter
- #DerefChar
- #EscapeChar
- #HotkeyInterval (使用 A_HotkeyInterval)
- #HotkeyModifierTimeout (使用 A_HotkeyModifierTimeout)
- #IfWinActive, #IfWinExist, #IfWinNotActive, #IfWinNotExist (请参阅 #HotIf Optimization)
- #InstallKeybdHook (使用 InstallKeybdHook 函数)
- #InstallMouseHook (使用 InstallMouseHook 函数)
- #KeyHistory (使用 `KeyHistory N`)
- #LTrim
- #MaxHotkeysPerInterval (使用 A_MaxHotkeysPerInterval)
- #MaxMem
- #MenuMaskKey (使用 A_MenuMaskKey)
- #NoEnv (现在是默认行为)

重命名的:

- ComObjCreate() → ComObject, 它现在是一个类
- ComObjParameter() → ComValue, 它现在是一个类
- DriveSpaceFree → DriveGetSpaceFree
- EnvAdd → DateAdd
- EnvSub → DateDiff
- FileCopyDir → DirCopy
- FileCreateDir → DirCreate
- FileMoveDir → DirMove
- FileRemoveDir → DirDelete
- FileSelectFile → FileSelect
- FileSelectFolder → DirSelect
- #If → #HotIf
- #IfTimeout → HotIfTimeout
- StringLower → StrLower 和 StrTitle
- StringUpper → StrUpper 和 StrTitle
- UrlDownloadToFile → Download
- WinMenuSelectItem → MenuSelect
- LV, TV 和 SB 函数 → GuiControl 的方法
- File.__Handle → File.Handle

### 修改的命令/函数

关于这一节的标题: v2 中没有命令, 只有函数. 标题引用两个版本.

`Chr(0)` 返回一个长度为 1 的字符串, 包含一个二进制零. 这是改进了对字符串中二进制零的支持的结果.

如果等待期过了, ClipWait 现在返回 0, 否则返回 1. ErrorLevel 被删除. 指定 0 不再等同于指定 0.5; 相反, 它产生尽可能短的等待.

`ComObj()`: 这个函数有一种通配符的名字, 允许许多不同的后缀. 有些名字通常用于特定类型的参数, 如 `ComObjActive(CLSID)`, `ComObjParameter(vt, value)`, `ComObjEnwrap(dsp)`. 取而代之的是单独的函数/类, 不再有通配符名称. 有关详情, 请参阅 COM 对象(ComObject).

Control: 对 Control 函数, SendMessage 和 PostMessage 使用的 Control 参数做了一些修改:

- 它现在可以接受 HWND(必须是一个纯整数) 或具有 `Hwnd` 属性的对象, 如 GuiControl 对象. HWND 可以识别一个控件或一个顶级窗口, 尽管后者通常只对少数几个函数才有意义(见下文).
- 它不再是可选的, 除了可以操作顶级窗口的函数(ControlSend[Text], ControlClick, SendMessage, PostMessage) 或前面有其他可选的参数时(ListViewGetContent, ControlGetPos, ControlMove).
- 如果省略, 就会使用目标窗口来代替. 这与之前 SendMessage/PostMessage 的行为相匹配, 并取代了 ControlSend 之前使用的 `ahk_parent` 特殊值.
- 空白的值是无效的. 函数从不默认目标窗口的最顶层的控件.

ControlGetFocus 现在返回控件的 HWND, 而不是其 ClassNN, 并且当它成功确定窗口没有焦点控件时, 不再认为有错误.

ControlMove, ControlGetPos 和 ControlClick 现在使用客户端坐标(像 GuiControl) 而不是窗口坐标. 客户端坐标是相对于客户端区域的左上方而言的, 它不包括窗口的标题栏和边框. (控件只在客户端区域内渲染.)

ControlMove, ControlSend 和 ControlSetText 现在使用与其他 Control 函数一致的参数顺序; 即 Control, WinTitle, WinText, ExcludeTitle, ExcludeText 总是组合在一起(在参数列表的最后), 以帮助记忆.

CoordMode 不再接受 "Relative" 作为一种模式, 因为所有模式都是相对于某个东西的. 它与 "Window" 同义, 所以用它代替.

DllCall: 请参阅下面的 DllCall 部分.

如果没有被注册 "edit" shell 动词, Edit 以前有针对 `.ini` 文件类型的后备行为. 由于脚本文件不需要有 `.ini` 扩展名. `AutoHotkey.ini` 是旧版本 AutoHotkey 中的默认脚本名称.

如果脚本是从 stdin 中读取的, 那么现在 Edit 不会做任何事情, 而会尝试为 `*` 打开一个编辑器.

以前, 当脚本不是持续运行时, Exit 作为 ExitApp 使用, 即使有其他挂起的线程被调用 Exit 的线程打断. 现在它不再这样做了. 相反, 它总是正确地退出当前线程, 并且(如果不是持续运行的) 脚本只在最后一个线程退出后才终止. 这保证了 `finally` 语句的执行和局部变量的释放, 这可能允许对局部变量所包含的任何对象调用 `__delete` .

FileAppend 默认为没有行末转换, 与 FileRead 和 FileOpen 一致. FileAppend 和 FileRead 都有一个单独的 Options 参数, 它取代了选项前缀, 并可能包括一个可选的编码名称(取代 FileRead 的 `*Pnnn` 选项). FileAppend, FileRead 和 FileOpen 使用 `"`n"` 来实现行末转换. FileAppend 和 FileRead 支持 `"RAW"` 选项来禁用编码转换(读/写二进制数据); FileRead 在这种情况下返回一个 `Buffer` 对象. 这取代了 `*c`(见文档中的 ClipboardAll). FileAppend 可以接受一个类 `Buffer` 对象, 在这种情况下不进行转换.

如果源路径不包含 `*` 或 `?` 并且没有找到文件, FileCopy 和 FileMove 现在抛出异常. 然而, 当源路径包含通配符时, 复制或移动零个文件仍然不被视为错误.

FileOpen 现在如果它不能打开文件, 则抛出异常. 否则, 在第一次尝试访问对象时就会抛出异常(如果脚本没有检查失败), 而不是在实际的失败点抛出异常.

File.RawRead: 当一个变量被直接传递时, 该变量的内部字符串缓冲的地址不再被使用. 因此, 一个包含地址的变量可以直接传递(而在 v1 版中, 类似 `var+0` 的东西是必须的).

对于由脚本分配的缓冲, 新的 `Buffer` 对象比变量更受欢迎; 可以使用任何对象, 但必须有 `Ptr` 和 `Size` 属性.

File.RawWrite: 和上面一样, 除了它可以接受一个字符串(或包含一个字符串的变量), 在这种情况下 Bytes 默认为字符串的字节大小. 字符串可以包含二进制的零.

File.ReadLine 现在总是支持 ``r`, ``n` 和 ``r`n` 作为行尾, 并且不再在返回值中包括行尾. 如果没有启用 EOL 转换, File.Read 仍会按原样返回给脚本.

FileEncoding 现在允许用数字来指定代码页, 而不使用 `CP` 前缀.

FileExist 现在忽略了每个目录列表中隐含的 `.` 和 `..`, 因此当目录存在但为空时, `FileExist("dir\*")` 现在是 false, 而不是 true.

如果源文件和目标文件是同一路径, 非编译脚本中的 FileInstall 不再尝试复制文件(在解析了相对路径之后, 因为源文件相对于 A_ScriptDir, 而不是 A_WorkingDir). 在 v1 中, 这导致 ErrorLevel 被设置为 1, 这通常不会被注意到. 试图通过两个不同的路径将一个文件复制到自身仍然会导致错误.

FileSelectFile(现在名为 FileSelect) 有两种多选模式, 可以通过选项 4 和 M 来访问. 选项 4 和相应的模式已经被删除; 它们已经有一段时间没有记录了. 当使用多选模式时, FileSelect 现在会返回一个路径的数组, 而不是像 `C:\Dir`nFile1`nFile2` 这样的字符串. 每个数组元素都包含一个文件的完整路径. 如果用户取消了, 数组就是空的.

FileSelect 现在使用 Windows Vista 及以后版本中的 IFileDialog API, 而不是以前的 GetOpenFileName/GetSaveFileName API. 这样就不需要使用与对话框改变当前工作目录有关的(内置) 解决方法.

当省略 Filter 时, FileSelect 默认不再有一个多余的 "Text Documents (*.txt)"/"文本文档(*.txt)" 过滤器.

FileSelect 不再从过滤器模式中剥离空格, 例如 `pattern with spaces*.ext` . 测试表明模式两侧的空格(例如 `*.cpp; *.h`) 中分号后的空格) 已经被操作系统忽略, 所以应该不会有负面影响.

FileSelect 现在可以通过 `D` 选项字母在 "选择文件夹" 模式下使用.

FileSetAttrib 现在可以在没有 +, - 或 ^ 前缀的情况下覆盖属性, 而不是什么都不做. 例如, `FileSetAttrib(FileGetAttrib(file2), file1)` 将 file2 的属性复制到 file1 中(添加 file2 有的属性并删除其没有的属性).

FileSetAttrib 和 FileSetTime: OperateOnFolders? 和 Recurse? 参数已被一个与 Loop Files 相同的 Mode 参数所取代. 例如, `FileSetAttrib("+a", "*.zip", "RF")`(只对 Files 进行递归操作).

GetKeyName 现在返回 VK 代码的非 Numpad 名称, 这些名称对应于 Numpad 和非 Numpad 键. 例如, `GetKeyName("vk25")` 返回 Left, 而不是 NumpadLeft.

GetKeyState 现在始终返回 0 或 1.

GroupActivate 现在返回被选中激活的窗口的 HWND, 如果没有匹配的窗口(除了已经激活的窗口), 则返回 0, 而不是设置 ErrorLevel.

GroupAdd: 删除了 Label 参数和相关功能. 这是一种不直观的方式, 用于检测 GroupActivate 未能找到任何匹配的窗口; 应该改为使用 GroupActivate 的返回值来代替.

GroupDeactivate 现在以更接近 `Alt`+`Esc` 和 `Alt`+`Shift`+`Esc` 系统热键和任务栏的方式选择窗口. 具体而言,

- 不计算拥有的窗口. 如果所有者窗口符合条件(与组不匹配), 所有者窗口或其拥有的一个窗口被激活; 以最后激活的为准. 一个由组成员拥有的窗口将不再被激活, 但将拥有的窗口本身添加到组中现在没有任何影响. (以前的行为是循环浏览每个拥有的窗口, 而从不激活其所有者.)
- 任何被禁用的窗口都会被跳过, 除非其拥有的一个窗口最近比它更活跃.
- 具有 WS_EX_NOACTIVATE 风格的窗口被跳过, 因为它们可能不应该被激活. 它们也会被 `Alt`+`Esc` 和 `Alt`+`Shift`+`Esc` 系统热键跳过.
- 具有 WS_EX_TOOLWINDOW 但不具有 WS_EX_APPWINDOW 的窗口被从任务栏和 Alt-Tab 中省略, 因此被跳过.

Hotkey 不再默认为脚本中最底层 #HotIf(以前是 `#If`). 热键/热字串和 HotIf 线程默认为与热键相同的条件, 因此 `Hotkey A_ThisHotkey, "Off"` 会关闭当前热键, 即使它是上下文相关的. 所有其他线程默认为自动执行部分使用的最后一个设置, 它本身默认为没有条件(全局热键).

Hotkey 的 Callback 参数现在需要一个函数对象或热键名称. 不再支持标签和函数名称. 如果指定了一个热键名称, 则使用该热键的原始函数; 而且与以前不同的是, 这与 #HotIf(以前的 #If) 一起工作.

- 其他好处, 这消除了以下特殊字符串的歧义: `On`, `Off`, `Toggle`, `AltTab`, `ShiftAltTab`, `AltTabAndMenu`, `AltTabMenuDismiss`. 以前的行为是使用该名称的标签/函数(如果存在的话), 但只有在 Label 参数不包含变量引用或表达式的情况下.

Hotkey 和 Hotstring 现在支持选项 S 以使热键/热串免于暂停 Suspend(相当于新的 #SuspendExempt 指令), 以及选项 S0 以禁止免于暂停.

"Hotkey If" 和其他 If 子命令被替换为单独的函数: HotIf, HotIfWinActive, HotIfWinExist, HotIfWinNotActive, HotIfWinNotExist.

HotIf(以前的 "Hotkey If") 现在可以识别使用 `and` 或 `or` 运算符的表达式. 这在 v1 中不起作用, 因为这些运算符在加载时被替换为 `&&` 或 `||`.

Hotkey 不再有 `UseErrorLevel` 选项, 也不再设置 ErrorLevel. 失败时将抛出异常. 错误信息被改为恒定的(而且更短), 在 `Exception.Extra` 中包含了按键或热键的名称, 而异常的类别则表示失败的原因.

#HotIf(以前的 #If) 现在隐式地创建了带有参数(ThisHotkey) 的函数. 如同所有函数的默认情况一样, 这个函数是假设-本地的. 该表达式可以创建局部变量和读取全局变量, 但不能直接赋值给全局变量, 因为该表达式不能包含声明.

`#HotIf` 已经被优化, 因此对 WinActive() 或 WinExist() 的简单调用可以由钩子线程直接计算(就像 `#IfWin` 在 v1 中一样, 而 `HotIfWin` 仍然是). 这提高了性能, 并减少了脚本繁忙/无响应时出现问题的风险. 该优化适用于包含对 WinActive() 或 WinExist() 的单次调用, 最多有两个参数的表达式, 其中每个参数是一个简单的带引号的字符串, 结果可以选择用 `!` 或 `not`. 例如, `#HotIf WinActive("Chrome")` 或 `#HotIf !WinExist("Popup")`. 在这些情况下, 具有任何给定标准组合的第一个表达式可以由表达式或窗口标准来确定. 例如, `HotIf '!WinExist("Popup")'` 和 `HotIfWinNotExist "Popup"` 指的是相同的热键变体.

`KeyHistory N` 调整按键历史缓冲的大小, 而不是显示按键历史. 这取代了 "#KeyHistory N".

ImageSearch 如果找到了图片, 则返回 true, 如果没有找到, 则返回 false, 如果不能进行搜索, 则抛出异常. 不设置 ErrorLevel.

IniDelete, IniRead 和 IniWrite 设置 A_LastError 为操作系统的 GetLastError() 函数的结果.

IniRead 抛出异常, 如果不能找到所请求的键, 段或文件, 并且省略了 Default 数. 如果 Default 被赋予一个值, 甚至是 `""`, 则不会抛出异常.

InputHook 将 `Shift`+`Backspace` 视为 `Backspace`, 而不是把它转录为 ``b`.

InputBox 进行了语法上的革新, 使其更容易使用(参数更少). 使用方法, 请参阅 InputBox.

InStr 的 CaseSensitive 参数已被 CaseSense 替换, 它可以是 0, 1 或 "Locale".

现在, InStr 在 Occurrence 为负数时从右向左搜索(以前会导致结果为 0), 如果在 Occurrence 为正数时使用负的 StartingPos 则不再从右向左搜索. (然而, 如果 StartingPos 是负的, 而 Occurrence 是省略的, 它仍然从右向左搜索.) 这有利于在循环中进行从右到左的搜索, 并允许在使用负的 StartingPos 的同时仍然从左到右搜索.

- 例如, `InStr(a, b,, -1, 2)` 现在从左到右搜索. 如果要从右向左搜索, 请使用 `InStr(a, b,, -1, -2)`.
- 注意 StartingPos 为 -1 意味着 v2 中的最后一个字符, v1 中的倒数第二个字符. 如果上面的例子来自 v1(而不是 v2.0-a033 - v2.0-a136), 新代码应该是 `InStr(a, b, -2, -2)`.

KeyWait 现在如果等待期过了就返回 0, 否则返回 1. 删除了 ErrorLevel.

MouseClick 和 MouseClickDrag 不再受交换鼠标按钮的系统设置的影响; "Left" 始终是主按钮, 而 "Right" 是副按钮.

MsgBox 的语法发生了变化, 以优先考虑其最常用的参数, 并提高了易用性. 有关使用情况, 请参阅下面的 MsgBox.

NumPut/NumGet: 当直接传递变量时, 不再使用该变量的内部字符串缓冲的地址. 因此, 一个包含地址的变量可以直接传递(而在 v1 中, 类似 `var+0` 的东西是必须的). 对于由脚本分配的缓冲, 新的 `Buffer` 对象比变量更优先; 任何对象都可以使用, 但必须有 `Ptr` 和 `Size` 属性.

NumPut 的参数被重新排序, 以允许一连串的值, 每个数字前面都有(现在是强制性的) 类型字符串. 例如: `NumPut("ptr", a, "int", b, "int", c, addrOrBuffer, offset)`. 类型现在对于 NumGet 也是必须的. (与 DllCall 相比, NumPut 的输入参数对应于 dll 函数的参数, 而 NumGet 的返回类型参数对应于 dll 函数的返回类型字符串.)

使用 `Object(obj)` 和 `Object(ptr)` 在引用和指针之间的转换被转移到单独的函数, `ObjPtrAddRef(obj)` 和 `ObjFromPtrAddRef(ptr)`. 这些函数也有不增加引用计数的版本: `ObjPtr(obj)` 和 `ObjFromPtr(ptr)`.

OnClipboardChange 标签不再被自动调用, 如果它存在的话. 使用在 v1.1.20 版本中添加的 OnClipboardChange 函数来代替. 现在它需要一个函数对象, 而不是一个名称.

OnError 现在需要一个函数对象, 而不是一个名称. 另请参阅下面的: 错误处理.

OnExit 命令已被移除; 请使用 v1.1.20 中添加的 OnExit 函数. 现在它需要一个函数对象, 而不是一个名称. A_ExitReason 也被删除了; 它的值可以作为 OnExit 回调函数的一个参数使用.

OnMessage 不再具有传递函数名(字符串) 时使用的单函数每条消息模式; 它现在只接受一个函数引用. 使用 `OnMessage(x, MyFunc)` 其中 MyFunc 是原义上的函数名称, 但请注意, V1 的等价物是 `OnMessage(x, Func("MyFunc"))`, 允许其他函数继续监视消息 x, 这与 `OnMessage(x, "MyFunc")` 不同. 要停止监测消息, 请使用 `OnMessage(x, MyFunc, 0)`, 因为 `OnMessage(x, "")` 和 `OnMessage(x)` 现在是错误的. 失败时, OnMessage 抛出异常.

当在热键的第一行使用时, Pause 不再豁免于 #MaxThreadsPerHotkey, 所以 `#p::Pause` 不再适用于切换暂停. 因此, `Pause()` 现在只暂停当前线程(对于像 ListVars/Pause 这样的组合), 而 `Pause(v)` 现在始终在底层线程上操作. `v` 必须是 0, 1 或 -1. 第二个参数被移除.

PixelSearch 和 PixelGetColor 使用 RGB 值而不是 BGR, 以便与其他函数保持一致. 如果出现问题, 这两个函数都会抛出异常, 并且不再设置 ErrorLevel. 如果找到了颜色, PixelSearch 返回 true. PixelSearch 的慢速模式已被删除, 因为它在大多数现代系统上是不可用的, 因为它与桌面合成不兼容.

PostMessage: 请参阅下面的 SendMessage.

Random 已被重做, 以利用操作系统的随机数生成器, 取消了一些限制, 并使其使用起来更加方便.

- 现在支持完整的 64 位有符号整数值范围(从 32 位增加).
- 浮点数由 53 位的随机整数生成, 而不是 32 位的随机整数, 并且应该大于或等于 Min 且小于 Max(但是浮点数的舍入误差理论上可以产生等于 Max 的结果).
- 这些参数已经可以以任何顺序指定, 但现在只指定第一个参数时, 会将其他边界默认为 0 而不是 2147483647. 例如, `Random(9)` 返回一个 0 到 9 之间的数字.
- 如果省略了两个参数, 则返回值是一个介于 0.0(包括) 和 1.0(一般不包括) 之间的浮点数, 而不是 0 和 2147483647(包括) 之间的整数.
- 系统会自动给随机数生成器播种, 不提供手动播种的方法, 所以无法替代 NewSeed 参数.

RegExMatch 选项 O 和 P 被删除; O(对象) 模式现在是强制性的. RegExMatch 对象现在支持枚举(for-loop). 匹配对象的语法已经改变:

- __Get 被用来实现速记的 `match.subpat` 其中 subpat 是一个子模式/捕获组的名称. 因为如果一个属性被 inherited, __Get 不再被调用, 以下的子模式名称不能再使用速记语法: Pos, Len, Name, Count, Mark. (例如, `match.Len` 总是返回整个匹配的长度, 而不是一个捕获的字符串.)
- 最初, 匹配对象有方法而不是属性, 这样属性就可以保留给子模式名称, 由于新的语言行为意味着 `match.name` 默认会返回一个函数, 方法已经被替换为或补充为属性:
  - Pos, Len 和 Name 现在是属性和方法.
  - Name 现在需要一个参数以避免混淆(`match.Name` 抛出一个错误).
  - Count 和 Mark 现在只是属性.
  - Value 已被删除; 使用 `match.0` 或 `match[]` 而不是 `match.Value()`, 和 `match[N]` 而不是 `match.Value(N)`.

RegisterCallback 被重命名为 CallbackCreate 并被改为更好地利用闭包:

- 它现在支持函数对象(而不再支持函数名称).
- 删除了 EventInfo 参数(使用闭包或绑定函数代替).
- 删除了可变参回调函数的特殊行为, 并增加了 `&` 选项(传递参数列表的地址).
- 增加了 `CallbackFree(Address)`, 以释放回调内存并释放关联的函数对象.

注册表函数(RegRead, RegWrite, RegDelete): 在 v1.1.21+ 中增加的新语法现在是唯一的语法. 根键和子键被合并. 不写 `RootKey, Key`, 而写 `RootKey\Key`. 要连接到一个远程注册表, 使用 `\\ComputerName\RootKey\Key` 而不是 `\\ComputerName:RootKey, Key`.

RegWrite 的参数被重新排序, 像 IniWrite 一样把 Value 放在前面(但这并不影响单参数模式, 其中 Value 是唯一参数).

当 KeyName 被省略, 并且当前的循环注册项目是一个子键时; RegDelete, RegRead 和 RegWrite 现在对该子键内的值进行操作; 即在这种情况下, KeyName 默认为 `A_LoopRegKey "\" A_LoopRegName`(注意, A_LoopRegKey 与 A_LoopRegSubKey 合并了). 以前它们的表现如下:

- RegRead 读取一个与子键同名的值, 如果父键中存在这样的子键.
- RegWrite 返回一个错误.
- RegDelete 删除该子键.

RegDelete, RegRead 和 RegWrite 现在允许在省略 KeyName 时指定 ValueName:

- 如果当前循环注册表项是一个子键, ValueName 默认为空(子键的默认值) 并且必须指定 ValueType.
- 如果当前的循环注册表项是一个值, ValueName 和 ValueType 默认为该值的名称和类型, 但其中一个或两个可以被覆盖.

否则, 带有空白或省略 ValueName的 RegDelete 现在会删除键的默认值(而不是键本身), 以便与 RegWrite, RegRead 和 A_LoopRegName 一致. 短语 "AHK_DEFAULT" 不再有任何特殊含义. 要删除一个键, 请使用 RegDeleteKey(新).

RegRead 现在有一个 Default 参数, 像 IniRead 一样.

RegRead 有一个未记录的 5 参数模式, 即在输出变量之后指定值类型. 这已被删除.

如果脚本是从 stdin 读取的, 那么现在 Reload 将不做任何事情.

Run 和 RunWait 不再识别 `UseErrorLevel` 选项, 因为 ErrorLevel 已被删除. 使用 try/catch 代替. A_LastError 被无条件地设置, 并且可以在捕获/抑制异常后进行检查. RunWait 返回退出代码.

Send(及其变体) 现在以一种与热键和 Click 一致的方式解释 `{LButton}` 和 `{RButton}`. 也就是说, LButton 是主按钮, RButton 是次按钮, 即使用户通过系统设置交换了这些按钮.

SendMessage 和 PostMessage 现在要求 wParam 和 lParam 是整数或具有 Ptr 属性的对象; 如果它们被赋予一个非数字的字符串或浮点, 则抛出异常. 以前, 如果表达式以 `"` 开头, 则通过地址传递字符串, 但其他字符串被强制为整数. 传递一个变量的地址(以前是 `&var`, 现在是 `StrPtr(var)`) 不再更新变量的长度(使用 `VarSetStrCapacity(&var, -1)`).

SendMessage 和 PostMessage 现在在失败(或超时) 时抛出异常, 并且不设置 ErrorLevel. SendMessage 返回消息回复.

SetTimer 不再支持标签或函数名称, 但由于它现在接受表达式, 并且函数可以直接通过名称引用, 用法看起来非常相似: `SetTimer MyFunc`. 与所有其他接受对象的函数一样, SetTimer 现在允许返回对象的表达式(以前它需要一个变量引用).

Sort 得到了以下变化:

- VarName 参数已被分成独立的输入/输出参数, 以增加灵活性. 现在的用法是 `Output := Sort(Input [, Options, Function])`.
- 当任何两个项目比较相等时, 项目的原始顺序将自动作为决定顺序的因素, 以确保结果更加稳定.
- `C` 选项现在也接受相当于其他函数的 CaseSense 参数的后缀(除 `CL` 外): `CLocale CLogical COn C1 COff C0`. 特别是, 对 "logical" 比较模式的支持是新加入的.

Sound 函数: 修改了 SoundGet 和 SoundSet 以更好地匹配 Vista+ 声音 API 的功能, 放弃了对 XP 的支持.

- 删除了不支持的控制类型.
- 删除了传统的混音器组件类型.
- 让组件通过名称和/或索引来引用.
- 让设备通过名称-前缀和/或索引来引用.
- 分成独立的音量和静音功能.
- 增加了 SoundGetName 用于检索设备或组件名称.
- 增加了 SoundGetInterface 用于检索 COM 接口.

StrGet: 如果 Length 是负数, 它的绝对值表示要转换的确切字符数, 包括字符串可能包含的任何二进制零值--换句话说, 结果总是一个正好是这个长度的字符串. 如果 Length 是正数, 转换后的字符串在第一个二进制 0 处结束, 就像在 v1 中一样.

StrGet/StrPut: Address 参数可以是一个具有 `Ptr` 和 `Size` 属性的对象, 比如新的 `Buffer` 对象. 读/写会自动受到 `Size`(单位为字节) 的限制. 如果还指定了 Length 它不能超过 Size(对于 UTF-16 来说乘以 2).

StrPut 的返回值现在是以字节为单位, 所以它可以直接传递给 Buffer().

StrReplace 现在有一个 CaseSense 参数来代替 OutputVarCount, 它被移到了一个参数的右边, Limit 在它后面.

Suspend: 在热键或热串的第一行调用 Suspend 不再自动使其免于挂起. 相反, 使用 `#SuspendExempt` 或 `S` 选项. "Permit" 参数值不再有效.

Switch 现在默认对字符串进行区分大小写的比较, 并且有一个 CaseSense 参数, 可以覆盖区分大小写的模式, 强制进行字符串(而不是数字) 比较. 以前, 只有当 StringCaseSense 被改为 On 时, 它才区分大小写.

SysGet 现在只有数字子命令; 它的其他子命令已经被分割成函数. 有关详情, 请参阅子命令.

TrayTip 的用法已改为 `TrayTip [Text, Title, Options]`. Options 是一个由零个或多个不区分大小写的选项组成的字符串, 以空格或制表符分隔. 选项是 `Iconx`, `Icon!`, `Iconi`, `Mute` 和/或任何数值, 如以前一样. 即使省略了 Text 也会显示(现在比 V1 版更难意外地显示). Seconds 参数不再存在(它在 Windows Vista 或更高版本中没有作用). 脚本现在可以结合使用 NIIF_USER(0x4) 和 NIIF_LARGE_ICON(0x20) 标记 (0x24) 来在通知中包含大版本的托盘图标. NIIF_USER(0x4) 也可以单独用于小图标, 但无法在所有操作系统中产生一致的结果.

#Warn UseUnsetLocal 和 UseUnsetGlobal 已经被删除, 因为现在读取一个未设置的变量会抛出错误. 可以使用 IsSet 来避免错误, 也可以使用 try/catch 或 OnError 来处理它.

添加了 #Warn VarUnset 它默认为 MsgBox. 如果没有禁用, 在变量从未被用作直接的, 非动态的赋值或引用运算符(&) 的目标, 或直接传递给 IsSet 时, 对于每个变量的第一个非动态引用会给出警告.

#Warn Unreachable 不再认为 `Exit` 调用后的行是不可达的, 因为 Exit 现在是一个普通函数.

#Warn 已被删除, 因为顶级类不再被赋值所覆盖. (然而, 它们现在可以被一个局部变量隐式地覆盖; 这可以被 #Warn LocalSameAsGlobal 检测到.)

WinActivate 现在在第一次尝试激活一个窗口失败后会发送 `{Alt up}` 测试表明这减少了任务栏按钮闪烁的情况. 有关详情, 请参阅相关文档.

WinSetTitle 和 WinMove 现在使用与其他 Win 函数一致的参数顺序; 即 WinTitle, WinText, ExcludeTitle, ExcludeText 总是组合在一起(在参数列表的最后), 以帮助记忆.

各种函数的 WinTitle 参数现在可以接受一个 HWND(必须是纯整数) 或一个具有 `Hwnd` 性的对象, 如一个 Gui 对象. 在这种情况下, `DetectHiddenWindows` 会被忽略.

WinMove 不再对原义单词 `DEFAULT` 进行特殊处理. 省略该参数或指定一个空字符串代替(这适用于 v1 和 v2).

WinWait, WinWaitClose, WinWaitActive 和 WinWaitNotActive 在等待结束(超时未过) 时返回非零值). ErrorLevel 被删除. WinWait 和 WinWaitActive 返回找到的窗口的 HWND. WinWaitClose 现在设置了最后找到的窗口, 所以如果 WinWaitClose 超时, 它返回 false, `WinExist()` 返回最后找到的窗口. 对于超时, 指定 0 不再等同于指定 0.5; 相反, 它产生了尽可能短的等待.

未分类:

InStr, SubStr, RegExMatch 和 RegExReplace 的负的 StartingPos 被解释为从末尾开始的位置. 位置 -1 是最后一个字符, 位置 0 是无效的(而在 V1 中, 位置 0 是最后一个字符).

以前接受 On/Off 或 On/Off/Toggle(但不接受其他字符串) 的函数现在需要 1/0/-1 来代替. On 和 Off 通常会被替换成 `True` 和 `False`. 原先返回 On/Off 的变量现在返回 1/0, 这在表达式中更加有用.

- #UseHook 和 #MaxThreadsBuffer 允许 `1`, `0`, `True` 和 `False`. (不像其他的, 它们实际上不支持表达式.)
- ListLines 允许空白或布尔值.
- ControlSetChecked, ControlSetEnabled, Pause, Suspend, WinSetAlwaysOnTop 和 WinSetEnabled 允许 `1`, `0` 和 `-1`.
- A_DetectHiddenWindows, A_DetectHiddenText 和 A_StoreCapsLockMode 使用布尔值(相应的函数也是如此).

以下函数返回一个纯整数, 而不是十六进制字符串:

- ControlGetExStyle
- ControlGetHwnd
- ControlGetStyle
- MouseGetPos
- WinActive
- WinExist
- WinGetID
- WinGetIDLast
- WinGetList (在数组中)
- WinGetStyle
- WinGetStyleEx
- WinGetControlsHwnd(在数组中)

A_ScriptHwnd 返回纯整数.

#### DllCall

如果类型参数是变量, 该变量的内容总是被使用, 而不是它的名称. 换句话说, 不再支持不加引号的类型名 - 类型名必须用引号括起来.

当 DllCall 更新以 Str 或 Str 形式传递的变量的长度时, 它现在会检测字符串是否正确地空终止(可能表明发生了缓冲溢出), 如果是这样, 会以错误消息终止程序, 因为不能保证安全执行.

`AStr`(没有任何后缀) 现在是只输入的. 因为缓冲永远只和输入字符串一样大, 所以它通常对输出参数没有用. 如果 AutoHotkey 被编译为 ANSI, 这将适用于 WStr 而不是 AStr, 但官方的 v2 版本只被编译为 Unicode.

如果一个函数向 `Str*`, `AStr*` 或 `WStr*` 参数写入一个新的地址, DllCall 现在会将新的字符串分配给相应的变量(如果有的话), 而不是仅仅更新原始字符串的长度(可能没有变化). 这种类型的参数通常不是用来修改输入的字符串, 而是在新的地址传回一个字符串.

DllCall 现在为任何 `Ptr` 参数和 Function 参数接受一个对象; 该对象必须有一个 `Ptr` 属性. 对于脚本分配的缓冲, 新的 `Buffer` 对象优先于变量. 对于 `Ptr*`, 参数的新值被分配回对象的 `Ptr` 属性. 这允许诸如 `DllCall(..., "Ptr*", unk := IUnknown.new())` 这样的结构, 与 `DllCall(..., "Ptr*", punk), unk := IUnknown.new(punk)` 相比, 减少了重复, 并且可以用来确保函数的任何输出被正确释放(即使由于 `HRESULT` 返回类型而抛出异常, 尽管通常在这种情况下函数不会输出一个非空指针).

DllCall 现在要求数字型参数的值必须是数字, 如果给出的是非数字或空字符串, 则会抛出异常. 特别是, 如果输出参数使用了 * 或 P 的后缀, 输出变量需要被初始化.

如果脚本传递一个包含数字的普通变量, 那么带有 * 或 P 后缀的数字参数的输出值(如果有的话) 会被忽略. 要接收输出值, 请传递一个 `VarRef`, 如 `&myVar` 或一个具有 `Ptr` 属性的对象.

如果函数失败, 新的 `HRESULT` 返回类型会抛出异常(`int < 0` 或 `uint & 0x80000000`). 这应该只用于实际返回 `HRESULT` 的函数.

#### Loop 子命令

子命令关键词必须按原义书写, 不能用引号括起来, 也不能是一个变量或表达式. 所有其他参数都是表达式. 所有循环子命令现在都支持 OTB.

已删除的:

    Loop, FilePattern [, IncludeFolders?, Recurse?]
    Loop, RootKey [, Key, IncludeSubkeys?, Recurse?]

使用下面的方法(在 v1.1.21 中添加) 来代替:

    Loop Files, FilePattern [, Mode]
    Loop Reg, RootKey\Key [, Mode]

第二个单词后面的逗号现在是可选的.

A_LoopRegKey 现在包含根键和子键, 而 A_LoopRegSubKey 已被删除.

#### InputBox

    Obj := InputBox([Text, Title, Options, Default])

Options 参数接受零个或多个不区分大小写的选项的字符串, 以空格或制表符为界, 类似于 Gui 控件选项. 例如, 这包括所有支持的选项: `x0 y0 w100 h100 T10.0 Password*`. `T` 是超时, 而 `Password` 的用法与同等的 Edit 控件选项相同.

宽度和高度选项现在可以设置客户端区域的大小(不包括标题栏和窗口框架的区域), 因此对主题的依赖性较小.

如果 Title 参数是一个空字符串, 标题将是空白的. 它只有在完全省略时才默认为 A_ScriptName 与用户定义函数的可选参数一致.

Obj 是一个对象, 其属性是 `result`(包含 "OK", "Cancel" 或 "Timeout") 和 `value`.

#### MsgBox

    Result := MsgBox([Text, Title, Options])

Options 参数接受由零个或多个不区分大小写的选项组成的字符串, 以空格或制表符为界, 类似于 Gui 控件选项.

- `Iconx`, `Icon?`, `Icon!` 和 `Iconi` 设置图标.
- `Default` 后面紧跟一个整数, 设置第 n 个按钮为默认值.
- `T` 后面紧跟一个整数或浮点数, 设置超时, 单位是秒.
- `Owner` 后跟 HWND 设置所有者, 覆盖 `+OwnDialogs` 选项.
- 以下相互排斥的字符串之一设置按钮的选择: `OK`, `OKCancel`, `AbortRetryIgnore`, `YesNoCancel`, `YesNo`, `RetryCancel`, `CancelTryAgainContinue`, 或者只是用斜线分隔的首字母(`o/c`, `y/n`, 等等.), 或者只是没有斜线的首字母.
- 任何数字值, 与 v1 中的相同. 数字值可以与字符串选项相结合, 或者 Options 可以是纯整数.

返回值是按钮的名称, 不带空格. 这些都是与 IfMsgBox 在 v1 中使用的字符串相同.

如果 Title 参数是一个空字符串, 标题将是空白. 只有当完全省略时, 它才默认为 A_ScriptName, 与用户定义的函数的可选参数一致.

#### 子命令

Control, ControlGet, Drive, DriveGet, WinGet, WinSet 和 Process 的子命令已被替换为单独的函数, 而主命令已被删除. 一些函数的名称和用法已经改变. 新的用法如下所示:

    ; 其中 ... 表示可选的 Control, WinTitle, 等等.

    Bool  := ControlGetChecked(...)
    Bool  := ControlGetEnabled(...)
    Bool  := ControlGetVisible(...)
    Int   := ControlGetIndex(...)  ; 适用于 Tab, LB, CB, DDL
    Str   := ControlGetChoice(...)
    Arr   := ControlGetItems(...)
    Int   := ControlGetStyle(...)
    Int   := ControlGetExStyle(...)
    Int   := ControlGetHwnd(...)

             ControlSetChecked(TrueFalseToggle, ...)
             ControlSetEnabled(TrueFalseToggle, ...)
             ControlShow(...)
             ControlHide(...)
             ControlSetStyle(Value, ...)
             ControlSetExStyle(Value, ...)
             ControlShowDropDown(...)
             ControlHideDropDown(...)
             ControlChooseIndex(Index, ...)  ; 同样涉及 Tab
    Index := ControlChooseString(Str, ...)

    Index := ControlFindItem(Str, ...)
    Index := ControlAddItem(Str, ...)
               ControlDeleteItem(Index, ...)

    Int   := EditGetLineCount(...)
    Int   := EditGetCurrentLine(...)
    Int   := EditGetCurrentCol(...)
    Str   := EditGetLine(N [, ...])
    Str   := EditGetSelectedText(...)
               EditPaste(Str, ...)

    Str   := ListViewGetContent([Options, ...])

               DriveEject([Drive])
               DriveRetract([Drive])
               DriveLock(Drive)
               DriveUnlock(Drive)
               DriveSetLabel(Drive [, Label])

    Str   := DriveGetList([Type])
    Str   := DriveGetFilesystem(Drive)
    Str   := DriveGetLabel(Drive)
    Str   := DriveGetSerial(Drive)
    Str   := DriveGetType(Path)
    Str   := DriveGetStatus(Path)
    Str   := DriveGetStatusCD(Drive)
    Int   := DriveGetCapacity(Path)
    Int   := DriveGetSpaceFree(Path)

    ; 其中 ... 表示可选的 WinTitle, 等等.

    Int   := WinGetID(...)
    Int   := WinGetIDLast(...)
    Int   := WinGetPID(...)
    Str   := WinGetProcessName(...)
    Str   := WinGetProcessPath(...)
    Int   := WinGetCount(...)
    Array := WinGetList(...)
    Int   := WinGetMinMax(...)
    Array := WinGetControls(...)
    Array := WinGetControlsHwnd(...)
    Int   := WinGetTransparent(...)
    Str   := WinGetTransColor(...)
    Int   := WinGetStyle(...)
    Int   := WinGetExStyle(...)

               WinSetTransparent(N [, ...])
               WinSetTransColor("Color [N]" [, ...]),
               WinSetAlwaysOnTop([TrueFalseToggle := -1, ...])
               WinSetStyle(Value [, ...])
               WinSetExStyle(Value [, ...])
               WinSetEnabled(Value [, ...])
               WinSetRegion(Value [, ...])

               WinRedraw(...)
               WinMoveBottom(...)
               WinMoveTop(...)

    PID   := ProcessExist([PID_or_Name])
    PID   := ProcessClose(PID_or_Name)
    PID   := ProcessWait(PID_or_Name [, Timeout])
    PID   := ProcessWaitClose(PID_or_Name [, Timeout])

             ProcessSetPriority(Priority [, PID_or_Name])

ProcessExist, ProcessClose, ProcessWait 和 ProcessWaitClose 不再设置 ErrorLevel; 相反, 它们返回 PID.

其他函数都不设置 ErrorLevel. 相反, 它们在失败时抛出异常. 在大多数情况下, 失败是因为没有找到目标窗口或控件.

HWND 和样式总是以纯整数形式返回, 而不是十六进制字符串.

ControlChooseIndex 允许 0 来取消对当前项目/所有项目的选择. 它取代了控件选择, 但也支持 Tab 控件.

ControlGet 被合并到 ControlGetIndex, 它也适用于 ListBox, ComboBox 和 DDL. 对于 Tab 控件, 如果没有选择 Tab, 它将返回 0(很少见, 但有效). ControlChooseIndex 不允许 Tab 控件使用 0, 因为应用程序往往不会处理它.

ControlGetItems 取代了 ListBox 和 ComboBox 的 ControlGet List. 它返回一个数组.

DriveEject 和 DriveRetract 现在使用 DeviceIoControl 而不是 mciSendString. DriveEject 能够弹出在资源管理器中有 "弹出" 选项的非 CD/DVD 驱动器(即可移动驱动器, 但不包括显示为固定磁盘的外部硬盘).

ListViewGetContent 取代了 ListView 的 ControlGet List, 目前的用法与以前相同.

WinGetList, WinGetControls 和 WinGetControlsHwnd 返回数组, 而不是以新行分隔的列表.

WinSetTransparent 将 "" 视为 "Off", 而不是 0(这将使窗口不可见且无法点击).

缩写的别名, 如 Topmost, Trans, FS 和 Cap 被删除.

以下函数以前是 SysGet 的子命令:

    Exists  := MonitorGet(N, Left, Top, Right, Bottom)
    Exists  := MonitorGetWorkArea(N, Left, Top, Right, Bottom)
    Count   := MonitorGetCount()
    Primary := MonitorGetPrimary()
    Name    := MonitorGetName(N)

### 新的函数

`Buffer(Size, FillByte)`(调用 Buffer 类) 创建并返回一个封装了 Size 字节的内存块的 `Buffer` 对象, 只在 FillByte 被指定时才初始化. `BufferObj.Ptr` 返回地址, 而 `BufferObj.Size` 返回或设置字节大小(重新分配内存块). 任何具有 Ptr 和 Size 属性的对象都可以传递给 NumPut, NumGet, StrPut, StrGet, File.RawRead, File.RawWrite 和 FileAppend. 任何具有 `Ptr` 属性的对象都可以被传递给 DllCall 的 `Ptr` 类型, SendMessage 和 PostMessage 参数.

`CaretGetPos([&X, &Y])` 检索光标(文本插入点) 的当前坐标. 这确保了 X 和 Y 坐标总是匹配的, 并且没有缓存导致意外的行为(比如 A_CaretX/Y 返回一个不在当前 CoordMode 中的值).

`ClipboardAll([Data, Size])` 创建一个包含剪贴板上所有内容的对象(可选择接受先前从剪贴板上获取的数据, 而不是使用剪贴板的当前内容). 读和写剪贴板文件数据的方法是不同的. 数据格式是一样的, 只是数据大小总是 32 位的, 这样数据就可以在 32 位和 64 位的构建之间移植. 详情见 v2 版文档.

`ComCall(offset, comobj, ...)` 等同于 `DllCall(NumGet(NumGet(comobj.ptr) + offset * A_Index), "ptr", comobj.ptr, ...)`, 但返回类型默认为 "hresult", 而不是 "int".

ComObject(以前是 ComObjCreate) 和 ComObjQuery 现在返回一个包装对象, 即使指定了一个 IID. ComObjQuery 允许第一个参数是任何具有 `Ptr` 属性的对象.

ControlGetClassNN 返回指定控件的 ClassNN.

ControlSendText 等同于 ControlSendRaw 但使用 {Text} 模式而不是 {Raw} 模式.

`DirExist(Path)` 用法类似于 FileExist. 注意 `InStr(FileExist(Pattern), "D")` 只告诉你 第一个 匹配的文件是否是一个文件夹, 而不是一个文件夹是否存在.

`Float(v)`: 请参阅类型.

`InstallKeybdHook(Install := true, Force := false)` 和 `InstallMouseHook(Install := true, Force := false)` 替换相应的指令, 为了提高稳定性.

`Integer(v)`: 请参阅类型.

isXXX: 传统的命令 "if var is type" 已经被一系列的函数取代: isAlnum, isAlpha, isDigit, isFloat, isInteger, isLower, isNumber, isSpace, isUpper, isXDigit. 除了 isFloat, isInteger 和 isNumber 外, 如果参数不是字符串, 则会抛出异常, 因为隐式转换为字符串可能会导致反直觉的结果.

`IsSet(var)`, `IsSetRef(&var)`: 如果变量已经被分配了一个值(即使该值是一个空字符串), 则返回真, 否则返回假. 如果是 false, 试图在表达式中读取该变量会出现错误.

`Menu()`/`MenuBar()` 返回一个新的 Menu/MenuBar 对象, 它有以下成员, 对应于 V1 Menu 子命令. 方法: Add, AddStandard, Check, Delete, Disable, Enable, Insert, Rename, SetColor, SetIcon, Show, ToggleCheck, ToggleEnable, Uncheck. 属性: ClickCount, Default, Handle(替换 MenuGetHandle). A_TrayMenu 也返回一个菜单对象. 没有 UseErrorLevel 模式, 没有全局菜单名称, 也没有显式删除菜单本身(当所有引用被释放时就会发生; Delete 等同于 v1 DeleteAll). 不支持标签, 只支持函数对象. AddStandard 方法添加了标准的菜单项, 并允许它们像自定义项一样被单独修改. 与 v1 不同, Win32 菜单只有在对象被删除时才会被销毁.

`MenuFromHandle(Handle)` 返回对应于 Win32 菜单句柄的菜单对象, 如果它是由 AutoHotkey 创建的.

`Number(v)`: 请参阅上面的类型.

`Persistent(Persist := true)` 替换相应的指令, 增加灵活性.

`RegDeleteKey("RootKey\SubKey")` 删除一个注册表键. (RegDelete 现在只删除值, 除非在注册表循环中省略所有参数.)

SendText 等同于 SendRaw, 但使用 {Text} 模式而不是 {Raw} 模式.

`StrCompare(str1, str2 [, CaseSense := false])` 返回 -1(str1 小于 str2), 0(相等) 或 1(大于). CaseSense 可以是 "Locale".

`String(v)`: 请参阅上面的类型.

`StrPtr(str)` 返回字符串的地址. 与 v1 中的取址不同, 它可以用于原义字符串和临时字符串.

`SysGetIPAddresses()` 返回一个 IP 地址的数组, 相当于已经被删除的 A_IPAddress 变量. 每个对 `A_IPAddress%N%` 的引用都会检索所有的地址, 但只返回一个, 所以检索多个地址所花费的时间是指数级的. 返回的数组可以有零个或多个元素. .

`TraySetIcon([FileName, IconNumber, Freeze])` 替换 "Menu Tray, Icon".

`VarSetStrCapacity(&Var [, NewCapacity])` 取代了 V1 的 VarSetCapacity, 但只用于 UTF-16 字符串(如优化重复连接); 因此 NewCapacity 和返回值的单位是字符而不是字节.

`VerCompare(A, B)` 使用与 #Requires 相同的算法对两个版本字符串进行比较.

`WinGetClientPos([&X, &Y, &W, &H, WinTitle, ...])` 检索窗口客户端区域的位置和大小, 以屏幕坐标表示.

### 新的指令

`#DllLoad [FileOrDirName]`: 在脚本开始执行前加载一个 DLL 或 EXE 文件.

### 内置变量

A_AhkPath 总是返回当前可执行文件/解释器的路径, 即使脚本已被编译. 以前, 如果使用 BIN 文件作为基础文件, 它会返回编译后的脚本的路径, 但 V2.0 版本不再包括 BIN 文件.

如果脚本没有被编译, A_IsCompiled 会返回 0 而不是 "".

A_OSVersion 总是返回格式为 `major.minor.build` 的字符串, 例如 `6.1.7601` 对应于 Windows 7 SP1. 由于只支持基于 NT 的系统, 因此删除了 A_OSType.

所有内置的 "虚拟" 变量现在都有 `A_` 前缀(具体细节见下文). 任何没有这个前缀的预定义变量(比如 `Object`) 都只是全局变量. 这种区别可能很重要, 因为目前不可能对一个虚拟变量进行引用(除非直接传递给一个内置函数); 然而, A_Args 不是一个虚拟变量.

返回数字的内置变量现在以整数, 而不是字符串的形式返回.

重命名:

- A_LoopFileFullPath → A_LoopFilePath (如果 Loop 的参数是相对的, 则返回相对路径, 所以 "full path" 会产生误解)
- A_LoopFileLongPath → A_LoopFileFullPath
- Clipboard → A_Clipboard

删除的:

- ClipboardAll(使用 ClipboardAll 函数代替)
- ComSpec(使用 A_ComSpec)
- ProgramFiles(使用 A_ProgramFiles)
- A_AutoTrim
- A_BatchLines
- A_CaretX, A_CaretY(使用 CaretGetPos)
- A_DefaultGui, A_DefaultListView, A_DefaultTreeView
- A_ExitReason
- A_FormatFloat
- A_FormatInteger
- A_Gui, A_GuiControl, A_GuiControlEvent, A_GuiEvent, A_GuiX, A_GuiY, A_GuiWidth, A_GuiHeight(全部替换为事件处理程序的参数)
- A_IPAddress1, A_IPAddress2, A_IPAddress3, A_IPAddress4(使用 SysGetIPAddresses)
- A_IsUnicode(v2 总是 Unicode; 它可以替换为 `StrLen(Chr(0xFFFF))` 或用 `global A_IsUnicode := 1` 重新定义)
- A_StringCaseSense
- A_ThisLabel
- A_ThisMenu, A_ThisMenuItem, A_ThisMenuItemPos(使用菜单项回调的参数)
- A_LoopRegSubKey(A_LoopRegKey 现在包含根键和子键)
- True 和 False(仍然存在, 但现在只是关键字, 而不是变量)

新增:

- A_AllowMainWindow(读/写; 替换 "Menu Tray, MainWindow/NoMainWindow")
- A_HotkeyInterval(替换 #HotkeyInterval)
- A_HotkeyModifierTimeout(替换 #HotkeyModifierTimeout)
- A_InitialWorkingDir(请参阅下面的默认设置)
- A_MaxHotkeysPerInterval(替换 #MaxHotkeysPerInterval)
- A_MenuMaskKey(替换 #MenuMaskKey)

以下内置变量可以被赋值:

- A_ControlDelay
- A_CoordMode..
- A_DefaultMouseSpeed
- A_DetectHiddenText(同样, 它现在返回 1 或 0, 而不是 "On" 或 "Off")
- A_DetectHiddenWindows(同样, 它现在返回 1 或 0, 而不是 "On" 或 "Off")
- A_EventInfo
- A_FileEncoding(同样, 它现在返回 "CP0" 代替 "", 并允许在赋值时省略 "CP" 前缀)
- A_IconHidden
- A_IconTip(同样, 它现在总是反映 tooltip, 即使它是默认的或空的)
- A_Index: 对于计数的循环, 修改它将影响执行多少次迭代. (内置变量的全局性质意味着枚举器函数可以设置索引, 以便 For 循环查看.)
- A_KeyDelay
- A_KeyDelayPlay
- A_KeyDuration
- A_KeyDurationPlay
- A_LastError: 调用 Win32 SetLastError() 函数. 同样, 它现在返回一个无符号值.
- A_ListLines
- A_MouseDelay
- A_MouseDelayPlay
- A_RegView
- A_ScriptName: 更改默认的对话框标题.
- A_SendLevel
- A_SendMode
- A_StoreCapsLockMode(同样, 它现在返回 1 或 0, 而不是 "On" 或 "Off")
- A_TitleMatchMode
- A_TitleMatchModeSpeed
- A_WinDelay
- A_WorkingDir: 等同于调用 SetWorkingDir.

### 内置对象

现在, 文件对象在调用属性时严格要求使用属性语法, 在调用方法时严格要求使用方法语法. 例如, `File.Pos(n)` 是无效的. 如果参数太少或太多, 或者只读属性被赋值, 则抛出异常.

`File.Tell()` 被删除.

`Func.IsByRef()` 现在可以与内置函数一起工作.

## Gui

Gui, GuiControl 和 GuiControlGet 被 Gui() 和 Gui/GuiControl 对象所取代, 它们通常更灵活, 更一致, 更容易使用.

GUI 通常不是通过名字/数字编号来引用的(尽管它仍然可以用 `GuiObj.Name` 命名). 相反, GUI 对象(和窗口) 是通过实例化 `Gui` 类显式创建的, 如 `GuiObj := Gui()`. 这个对象具有取代 Gui 子命令的方法和属性. `GuiObj.Add()` 返回一个 GuiControl 对象, 该对象具有替代 GuiControl 和 GuiControlGet 命令的方法和属性. 人们可以将这个对象存储在一个变量中, 或者使用 `GuiObj["Name"]` 或 `GuiCtrlFromHwnd(hwnd)` 来检索这个对象. 每当调用一个事件处理程序(g-label 的替换) 时, 它也会作为一个参数被传递.

这些方法和属性的用法不是 1:1. 许多部分已经被修改, 以使其更加一致和灵活, 并修复错误或限制.

没有 "默认" GUI, 因为目标 Gui 或控制对象总是被指定的. LV/TV/SB 函数被替换为(控件对象的) 方法, 使得使用多个 ListViews/TreeViews 更加容易.

没有包含事件信息的内置变量. 这些信息被作为参数传递给处理事件的函数/方法, 包括源 Gui 或控件.

控件仍然可以被命名, 并且可以通过名字来引用, 但它只是一个名称(与 `GuiObj["Name"]` 和 `GuiObj.Submit()` 一起使用), 而不是一个关联变量, 所以不需要声明或创建一个全局或静态变量. 该值绝不会自动存储在变量中, 而是通过 `GuiCtrl.Value` 进行访问. `GuiObj.Submit()` 返回一个以控件名称作为键的新关联数组.

vName 选项现在只是将控件的名称设置为 Name.

+HwndVarName 选项已被删除, 改为使用 `GuiCtrl.Hwnd`.

不再有 "g-labels" 或标签/函数来自动处理 GUI 事件. 脚本必须通过调用 Gui 或 GuiControl 的 OnEvent 方法来注册每个感兴趣的事件. 例如, 脚本不会在 g-label 中检查 `if (A_GuiEvent = "I" && InStr(ErrorLevel, "F", true))`, 脚本将为 `ItemFocus` 事件注册处理程序: `MyLV.OnEvent("ItemFocus", MyFunction)`. MyFunction 将仅为 ItemFocus 事件调用. 没有必要应用 `AltSubmit` 来启用其他事件.

数组被用于以前使用管道分隔的列表的地方, 例如在创建 ListBox 时, 在添加项目时, 或在检索选定的项目时, 为 ListBox 指定项目.

脚本可以定义一个 `extends Gui` 的类, 并处理它自己的事件, 使所有的 GUI 逻辑自成一体.

### Gui 子命令

Gui New → Gui(). 传递空标题(不省略) 现在会导致空标题, 而不是默认标题.

Gui Add → GuiObj.Add() 或 GuiObj.AddControlType(); 例如 `GuiObj.Add("Edit")` 或 `GuiObj.AddEdit()`.

Gui Show → GuiObj.Show(), 但它没有 Title 参数. 标题可以由 Gui() 的参数或通过 GuiObj.Title 属性来指定. 初始焦点仍然设置为具有 WS_TABSTOP 样式的第一个可输入控件(根据系统的默认消息处理), 除非是 Button 控件, 在这种情况下, 焦点现在转移到 Default(默认) 按钮上.

Gui Submit → GuiObj.Submit(). 除了 Submit() 创建并返回一个包含所有 "关联变量" 的新对象之外, 它的工作方式与以前类似.

Gui Destroy → GuiObj.Destroy(). 该对象仍然存在(直到脚本释放它), 但不能使用. 必须创建新的 GUI(如果需要). 删除对象时, 窗口也会被销毁, 但当窗口可见时, 该对象将 "保持活动状态".

Gui Font → GuiObj.SetFont(). 也可以使用 GuiCtrl.SetFont() 直接设置控件的字体.

Gui Color → GuiObj.BackColor 设置/返回背景颜色. 不支持 ControlColor(第二个参数), 但所有以前支持它的控件都可以使用 `+Background` 选项来设置背景. 与 Gui Color 不同, GuiObj.BackColor 不会影响 Progress 控件或禁用的/只读的 Edit, DDL, ComboBox 或 TreeView(带有 -Theme) 控件.

Gui Margin → GuiObj.MarginX 和 GuiObj.MarginY 属性.

Gui Menu → GuiObj.MenuBar 设置/返回使用 `MenuBar()` 创建的 MenuBar 对象.

Gui Cancel/Hide/Minimize/Maximize/Restore → 使用同名的 Gui 方法.

Gui Flash → GuiObj.Flash(), 但使用 `false` 代替 `Off`.

Gui Tab → TabControl.UseTab(). 与以前一样, 默认匹配选项卡名称的前缀. 对于第二个参数传递 true 以匹配整个选项卡名称, 但与 v1 "Exact" 模式不同, 它不区分大小写.

### 事件

关于所有显式支持的 GUI 和 GUI 控件事件的细节, 请参阅事件(OnEvent).

Size 事件传递 0, -1 或 1(与 WinGetMinMax 一致) 而不是 0, 1 或 2.

ContextMenu 事件可以为每个控件注册, 也可以为整个 GUI 注册.

DropFiles 事件交换了 FileArray 和 Ctrl 参数, 以便与 ContextMenu 一致.

ContextMenu 和 DropFiles 事件使用客户端坐标, 而不是窗口坐标(Client 也是 v2 中的默认的 CoordMode).

删除了以下控件事件, 但检测它们只需将适当的数字通知代码(在 Windows SDK 中定义) 传递给 GuiCtrl.OnNotify() 即可: K, D, d, A, S, s, M, C, E 和 MonthCal 的 1 和 2.

Control 事件不传递事件名称作为参数(GUI 事件从未传递过).

自定义的 N 和 Normal 事件被 GuiCtrl.OnNotify() 和 GuiCtrl.OnCommand() 取代, 它们可以用于任何控件.

Link 的 Click 事件传递 "Ctrl, ID 或 Index, HREF", 而不是 "Ctrl, Index, HREF 或 ID", 如果注册了 Click 回调, 则不会自动执行 HREF.

ListView 的 Click, DoubleClick 和 ContextMenu(当由右键触发时) 事件现在报告被点击的项目(如果没有则为 0), 而不是焦点项目.

ListView 的 I 事件被拆分为多个命名事件, 除了 f(取消焦点)事件, 因为它被 F(ItemFocus) 所隐含, 所以被排除.

ListView 的 e(ItemEdit) 事件在用户取消时被忽略.

Slider 的 Change 事件比 V1 的 g-label 事件更稳定地被引发; 也就是说, 它不再默认忽略鼠标滚轮的变化. 有关详情, 请参阅检测更改(Slider).

BS_NOTIFY 样式现在会根据需要自动添加到 Button, CheckBox 和 Radio 控件. 它不再被默认应用于 Radio 控件.

Focus(以前的 F) 和 LoseFocus(以前的 f) 支持更多的(但不是全部) 控件类型.

使用 Edit.Value 或 Edit.Text 设置 Edit 控件的文本不会触发该控件的 Change 事件, 而 GuiControl 会触发该控件的 g-label.

LV/TV.Add/Modify 现在抑制了项目变更事件, 所以这类事件只能由用户操作或 SendMessage 引发.

### 删除的

+Delimiter
+HwndOutputVar (使用 GuiObj.Hwnd 或 GuiCtrl.Hwnd 代替)
+Label
+LastFoundExist
Gui GuiName: Default

### 控件选项

对 +/-Background 的解释和支持更加一致. 所有支持 "Gui Color" 的控件现在都支持 `+BackgroundColor` 和 `+BackgroundDefault`(`-Background` 相同), 而不仅仅是 ListView/TreeView/StatusBar/Progress.

当使用 `xp`/`yp` 或 `xp+0`/`yp+0` 时, GuiObj.Add 默认为 `y+m`/`x+m`, 而不是 `yp`/`xp`. 换句话说, 控件被放置在前一个控件的下面/右边, 而不是在完全相同的位置. 如果使用非零偏移, 行为与 v1 相同. 要使用完全相同的位置, 请同时指定 `xp yp`.

`x+m` 和 `y+m` 后面可以跟一个额外的偏移量, 如 `x+m+10`(`x+m10` 也是有效的, 但可读性差).

`Choose` 不再是指定 MonthCal 值的一个多余的(无文档) 方式. 就像以前一样, 使用 Text 参数.

### GuiControlGet

#### 空子命令

GuiControlGet 的空子命令有两种模式: 默认模式和文本模式, 其中第四个参数是单词 `Text`. 如果控件类型没有单一的 "value", GuiControlGet 默认为返回 GetWindowText 的结果(不一定是可见文本). 有些控件没有可见文本, 或者不支持检索它, 所以完全忽略了第四个参数. 相比之下, GuiCtrl.Text 会返回显示文本, 隐藏文本(与 ControlGetText 返回的文本相同) 或者什么都没有.

下表显示了 GuiControlGet 的每种模式和控件类型的最接近的等价属性或函数.

控件 | 默认 | 文本 | 注意
--- | --- | --- | ---
ActiveX | .Value | .Text | 文本处于隐藏状态. 见下文.
Button | .Text |
CheckBox | .Value | .Text |
ComboBox | .Text | ControlGetText() | 如果使用了 AltSubmit, 请使用 Value 而不是 Text(但如果 Text 与列表项目不匹配,则 Value 返回 0). Text 执行大小写更正, 而 ControlGetText 返回 Edit 区域的内容.
Custom | .Text |
DateTime | .Value |
DDL | .Text | 如果使用了 AltSubmit, 请使用 Value 而不是 Text.
Edit | .Value |
GroupBox | .Text |
Hotkey | .Value |
Link | .Text |
ListBox | .Text | ControlGetText() | 如果使用了 AltSubmit, 请使用 Value 而不是 Text. Text 返回所选项目的文本, 而 ControlGetText 返回隐藏文本. 见下文.
ListView | .Text | 文本处于隐藏状态.
MonthCal | .Value |
Picture | .Value |
Progress | .Value |
Radio | .Value | .Text |
Slider | .Value |
StatusBar | .Text |
Tab | .Text | ControlGetText() | 如果使用了 AltSubmit, 请使用 Value 而不是 Text. Text 返回所选选项卡的文本, 而 ControlGetText 返回隐藏文本.
Text | .Text |
TreeView | .Text | 文本处于隐藏状态.
UpDown | .Value |

ListBox: 对于多选 ListBox, Text 和 Value 返回数组, 而不是管道分隔的列表.

ActiveX: GuiCtrl.Value 每次都会返回相同的对象, 而 GuiControlGet 每次都会创建一个新的封装对象. 因此, 为了保持 ComObjConnect 连接处于活动状态, 不再需要保留对 ActiveX 对象的引用.

#### 其他子命令

Pos → GuiCtrl.GetPos()

Focus → GuiObj.FocusedCtrl; 返回一个 GuiControl 对象而不是 ClassNN.

FocusV → `GuiObj.FocusedCtrl.Name`

Hwnd → GuiCtrl.Hwnd; 返回一个纯整数, 而不是十六进制的字符串.

Enabled/Visible/Name → 同名的 GuiCtrl 属性.

### GuiControl

#### (空) 和 Text 子命令

下表显示了 GuiControl 的每种模式和控件类型的最接近的等效属性或函数.

控件 | (Blank) | Text | 注意
--- | --- | --- | ---
ActiveX | N/A | 命令没有效果.
Button | .Text |
CheckBox | .Value | .Text |
ComboBox | .Delete/Add/Choose | .Text |
Custom | .Text |
DateTime | .Value | .SetFormat() |
DDL | .Delete/Add/Choose |
Edit | .Value |
GroupBox | .Text |
Hotkey | .Value |
Link | .Text |
ListBox | .Delete/Add/Choose |
ListView | N/A | 命令没有效果.
MonthCal | .Value |
Picture | .Value |
Progress | .Value | 使用 `+=` 运算符代替 `+` 前缀.
Radio | .Value | .Text |
Slider | .Value | 使用 `+=` 运算符代替 `+` 前缀.
StatusBar | .Text 或 SB.SetText() |
Tab | .Delete/Add/Choose |
Text | .Text |
TreeView | N/A | 命令没有效果.
UpDown | .Value | 使用 `+=` 运算符代替 `+` 前缀.

#### 其他子命令

Move → GuiCtrl.Move(x, y, w, h)

MoveDraw → GuiCtrl.Move(x, y, w, h), GuiCtrl.Redraw()

Focus → GuiCtrl.Focus(), 现在使用 WM_NEXTDLGCTL 而不是 SetFocus, 因此聚焦 Button 可以暂时将其设置为默认值, 这与制表时的控件一致.

Enable/Disable → set GuiCtrl.Enabled

Hide/Show → set GuiCtrl.Visible

Choose → GuiCtrl.Choose(n), 其中 n 为纯整数. 不支持 `|n` 或 `||n` 模式(如果需要, 使用 ControlChoose 代替).

ChooseString → GuiCtrl.Choose(s), 其中 s 不是一个纯整数. 不支持 `|n` 或 `||n` 模式. 如果字符串与多选列表框中的多个项目匹配, Choose() 将全部选择, 而不是只选择第一个.

Font → GuiCtrl.SetFont()

+/-Option → GuiCtrl.Opt("+/-Option")

### 其他更改

Progress Gui 控件不再具有默认的 PBS_SMOOTH 样式, 因此它们现在是根据系统的视觉样式来设计的.

当 DPI 大于 100% 时, 默认的边距和控件尺寸(尤其是 Button 控件) 可能与 v1 版略有不同.

当图片控件无法通过 `GuiCtrl.Value := "new image.png"` 设置新图片时, 不再删除当前图片. 但是, 允许通过 `GuiCtrl.Value := ""` 删除当前图像.

## 错误处理

在退出脚本之前, OnError 现在被调用来处理关键性的错误. 尽管脚本可能不处于安全的执行状态, 但仍会进行尝试, 这与 OnExit 一致.

运行时错误不再将 `Exception.What` 设置为当前正在运行的用户定义的函数或子函数(但在调用 `Error()` 时仍会这样做, 但没有第二个参数). 这给了 `What` 一个更明确的目的: 函数名称表示该函数的失败(而不是调用该函数或计算其参数的失败). 对于表达式计算和控制流错误, `What` 是空白的(其他一些也可能是空白).

由运行时错误抛出的异常对象现在可以被识别为新的 Error 类或更具体的子类的实例. Error 对象有一个 `Stack` 属性, 包含一个堆栈跟踪. 如果 What 参数指定了一个正在运行的函数的名称, 那么现在会根据哪一行调用该函数来设置 File 和 Line .

Try-catch 语法已经改变, 允许脚本捕捉特定的错误类, 而不捕捉其他错误. 有关详情, 请参阅下文的 Catch 章节.

### 可继续错误(不中断脚本运行)

在大多数情况下, 错误对话框现在提供了继续当前线程的选项(而不是退出线程). COM 错误现在在选择不继续时退出线程(而不是退出整个脚本).

脚本不应依赖于此. 如果错误是由一个内置函数引发的, 继续会导致它返回 "". 如果错误是由表达式计算器引发的(比如无效的动态引用或除以 0), 表达式会被中止, 并返回 ""(如果用作控制流语句的参数).

在某些情况下, 代码不支持继续, 继续的选项不应该被显示. 对于旨在终止脚本的关键错误, 也不显示该选项.

OnError 回调现在接受第二个参数, 包含以下值之一:

- "Return": 返回 -1 将继续执行线程, 而 0 和 1 则和以前一样.
- "Exit": 不支持继续执行. 返回非零将停止进一步的处理, 但仍然退出线程.
- "ExitApp": 这是一个关键错误. 返回非零值会停止进一步的处理, 但脚本仍然被终止.

### ErrorLevel

ErrorLevel 已经被删除. 脚本经常(也许通常) 是在没有错误检查的情况下编写的, 所以为错误设置 ErrorLevel 的策略经常让它们不被检测到. 即时的错误信息可能看起来有点对抗性, 但通常更有帮助.

在以前设置 ErrorLevel 以指示错误情况的地方, 会抛出异常, 并有一个(通常) 更有用的错误信息.

诸如 "Process Exist" 之类的命令使用它来返回一个值, 现在只是返回这个值(例如 `pid := ProcessExist()`) 或更有用的东西(例如 `hwnd := GroupActivate(group)`).

在某些情况下, ErrorLevel 被用于辅助返回值.

- 使用 U 选项的 Sort 不再返回删除的重复数.
- `Input` 被删除. 它被 InputHook 所取代. 几行代码可以做一个简单的替换, 返回一个包含结果的 InputHook 对象, 而不是使用 ErrorLevel 和一个 OutputVar.
- InputBox 返回一个带有 `result`(OK, Cancel 或 Timeout) 和 `value` 属性的对象.

以前在 ErrorLevel 中存储失败次数的文件函数现在在抛出的异常对象的 `Extra` 属性中抛出它.

SendMessage 超时通常是一种异常情况, 所以会导致抛出 `TimeoutError`. `TargetError` 和 `OSError` 可能会在其他条件下被抛出.

Run 和 Hotkey 函数的 `UseErrorLevel` 模式被移除. 这种模式早于 `Try`/`Catch` 添加到语言中. 菜单和 Gui 也有这种模式, 但被替换为对象(不使用 ErrorLevel).

### 表达式

与 v1 相比, 更多的语法错误会引发加载时错误, 如:

- 空括号(与函数名相邻的除外); 例如 `x ()`
- 前缀运算符使用错误或缺少操作数; 例如 `x!`
- 少于两个操作数的二元运算符.
- 少于三个操作数的三元运算符.
- 赋值的目标不是一个可写的变量或属性.

当发生以下任何故障时, 会抛出异常(而不是忽略该故障或产生一个空字符串):

- 试图对一个非数字值进行数学运算. (数字字符串是可以的.)
- 除以 0 或其他无效/不支持的输入, 如 `(-1)**1.5`. 注意, 有些情况会被新检测为无效, 如 `0**0` 和 `a<<b` 或 `a>>b`, 其中 `b` 的范围不在 0..63.
- 未能为内置函数的返回值, 串联或表达式的结果分配内存.
- 堆栈下溢(通常由语法错误引起).
- 试图向不是变量(或数组元素) 的东西赋值.
- 试图向一个只读变量赋值.
- 试图用一个空的名称进行双重解引, 如 `fn(%empty%)`.
- 未能执行动态函数调用或方法调用.
- 一个方法/属性调用失败, 因为该值没有实现该方法/属性. (对于 v1 中的关联数组, 只有方法调用可以导致这种情况.)
- 由于内存分配失败而导致对象分配失败.

上面的一些条件在 v1 中被检测到, 但在表达式中间没有被检测到; 例如, `A_AhkPath := x` 在 v1 中检测到, 但在 v2 中仅在 `y := x, A_AhkPath := x` 检测到.

独立使用运算符 `+=`, `-=`, `--` 和 `++` 不再将空变量视为 0. 这与 v1 不同, 在 v1 中, 独立使用运算符时将空变量视为 0, 但在表达式中间或与多语句逗号一起使用时则不会.

### 函数

函数在失败时通常会抛出异常. 特别是:

- 由于 DllCall, RegExMatch 和 RegExReplace 的不正确使用导致的错误由于其复杂性而相当常见, 而且(像许多错误一样) 如果立即显示错误信息, 则更容易发现和调试.

- 如果数学函数的任何输入不是数字, 或者操作无效(如除以 0), 则抛出异常.

- 带有 WinTitle 参数的函数(有例外, 如 WinClose 的 ahk_group 模式), 如果没有找到目标窗口或控件就会抛出.

对一些以前没有检测到的错误抛出了异常, 一些被错误地标记为错误的情况(以前是通过设置 ErrorLevel) 被修复.

某些 error 消息已经发生变化.

### Catch

Catch 的语法已经更改, 以提供一种方法来捕获特定的错误类, 而不捕获其他的错误(将控制权转移到调用堆栈更上层的另一个 Catch, 或报告错误并退出线程). 以前这需要捕捉所有类型的抛出值, 然后检查类型并重新抛出. 例如:

    ; 旧的(使用废弃的 v2.0-a 规则进行演示, 因为 v1 没有 `is` 或 Error 类)
    try
        SendMessage msg,,, "Control1", "The Window"
    catch err
        if err is TimeoutError
            MsgBox "The Window is unresponsive"
        else
            throw err

    ; 新的
    try
        SendMessage msg,,, "Control1", "The Window"
    catch TimeoutError
        MsgBox "The Window is unresponsive"

变化:

- `catch` 捕获一个错误实例.
- `catch as err` 捕捉一个 Error 实例, 它被分配给 err.
- `catch ValueError as err` 捕捉一个 ValueError 实例, 它被分配给 err.
- `catch ValueError, TypeError` 捕捉任一类型错误.
- `catch ValueError, TypeError as err` 捕获任一类型错误并将实例分配给 err.
- `catch Any` 捕捉 anything.
- `catch (MyError as err)` 允许使用括号, 就像大多数其他控制流语句一样.

如果使用 `try` 而不使用 `finally` 或 `catch` 它的行为就像有一个空块的 `catch` 虽然这听起来像 v1, 但现在 `catch` 本身只抓取 `Error` 的实例. 在大多数情况下, `try` 本身就是为了抑制一个 Error, 所以不需要做任何改变. 然而, v2 中与 v1 `try something()` 直接等价的是:

    try something()
    catch Any
    {}

优先考虑错误类型而不是输出变量名称可能会鼓励更好的代码; 按照预期处理预期的错误, 而不是压制或错误地处理本应报告的意外错误.

由于所有类型的值都可以被抛出, 任何类对过滤器来说都是有效的(例如 `String` 或 `Map`). 然而, 类的原型在加载时被解析, 必须被指定为一个完整的类名, 而不是一个任意的表达式(类似于 `class x extends y` 中的 `y`).

当 `catch` 语句正在执行时, `throw` 可以在没有参数的情况下被用来重新抛出异常(避免了为此目的指定一个输出变量的需要). 这甚至在嵌套的 `try`...`finally` 中也被支持, 但在嵌套的 `try`...`catch` 中不支持. `throw` 不需要被 `catch` 语句的主体所包含; 它可以被一个被调用的函数所使用.

在最后一个 `catch` 之后可以有一个 `else`; 如果在 `try` 中没有抛出异常, 就执行它.

## 键盘, 鼠标, 热键和热字串

更少的 VK 到 SC 和 SC 到 VK 的映射是硬编码的, 在理论上提高了与非常规的自定义键盘布局的兼容性.

删除了 "Return" 和 "Break" 键的名称. 使用 "Enter" 和 "Pause" 代替.

现在总是通过从键盘布局 DLL 读取 KLLF_ALTGR 标志来检测每个键盘布局上是否存在 `AltGr`. (v1.1.28+ 版本已经使用了这种方法.) 通过键盘钩子检测 `AltGr` 的后备方法已经被删除.

鼠标滚轮热键将 A_EventInfo 设置为由鼠标驱动程序报告的滚轮延迟, 而不是除以 120. 一般来说, 它是 120 的倍数, 但一些鼠标硬件/驱动程序可能会以更高的分辨率报告滚轮运动.

热串现在将 `Shift`+`Backspace` 视为 `Backspace`, 而不是在热串缓冲内将其转录为 ``b`.

热字串使用第一对冒号(`::`) 作为分隔符, 而不是在出现多对冒号时使用最后一对. 换句话说, 在 v2 中, 冒号(与另一个冒号相邻时) 必须在触发文本中转义, 而在 v1 中, 它们必须在替换中转义. 请注意, 对于奇数的连续冒号, 以前的行为不认为最后的冒号是一对冒号的一部分. 例如, 对于 `::1:::2`(`1` → `:2`) 的行为没有变化, 但 `::3::::4` 现在是 `3` → `::4` 而不是 `3::` → `4`.

热字串不再成对转义冒号, 这意味着现在可以在热串触发器的末尾转义一个冒号. 例如, `::5`:::6` 现在是 `5:` → `6` 而不是一个错误, 而 `::7`::::8` 现在是 `7:` → `:8` 而不是 `7::` → `8`. 在这些情况下, 最好转义每个原义的冒号, 以避免混淆(但单个孤立的冒号不需要转义).

带有延续片段的热字串现在默认为文本模式而不是原始模式.

热键现在只在 Win/Alt 键在逻辑上是向下的并且热键需要 Win/Alt 键(带 `#`/`!` 或自定义前缀) 的情况下才会在释放时屏蔽. 也就是说, 不需要 Win/Alt 键的热键在 Win/Alt 键物理上处于关闭状态时不再掩盖 Win/Alt 向上. 这使得发送 `{Blind}{LWin up}` 的热键可以激活开始菜单(如果使用 `AppsKey::RWin` 这样的重映射键, 这已经是可能的).

## 其他

已放弃对 Windows 2000 和 Windows XP 的支持.

AutoHotkey 不再在启动时覆盖系统的 `ForegroundLockTimeout` 设置.

- 这是通过调用带有 `SPI_SETFOREGROUNDLOCKTIMEOUT` 的 `SystemParametersInfo` 动作实现的, 它影响到当前用户会话的所有应用程序. 它在注销后不会持续存在, 但对一些用户来说仍是不受欢迎的.
- 用户的错误报告(和常识) 表明, 如果它起作用, 它允许焦点被那些没有专门设计的程序窃取.
- 在 Windows 10 上的一些测试表明, 它对任何事情都没有影响; 对 `SetForegroundWindow` 的调用总是失败的, 而 WinActivate 采用的其他变通方法是需要的, 并且无论超时与否都是有效的. `SPI_GETFOREGROUNDLOCKTIMEOUT` 是从一个单独的进程中使用的, 以验证该变化是否生效(它有时并不生效).
- 它可以很容易地在脚本中被复制:

      DllCall("SystemParametersInfo", "int", 0x2001, "int", 0, "ptr", 0, "int", 2)

RegEx 换行匹配默认为 (*ANYCRLF) 和 (*BSR_ANYCRLF); `r 和 `n 被识别, 除了 `r`n 之外. `a 选项隐含地启用了 (*BSR_UNICODE).

正则调出现在可以是可变的. 通过 `pcre_callout` 变量指定的调出可以是任何可调用的对象, 或 `pcre_callout` 本身可以直接定义为函数(可能是嵌套函数). 随着函数和变量命名空间的合并, 诸如 `(?C:fn)` 这样的调出模式也可以指代包含函数对象的局部或全局变量, 而不仅仅是用户定义的函数.

从 stdin 读取的脚本(例如用 `AutoHotkey.exe *`) 不再包括 A_ScriptFullPath 中的初始工作目录或主窗口的标题, 但它被用作 A_ScriptDir 和定位本地 Lib 文件夹.

由自动执行线程改变的设置现在会立即成为默认设置(对于在此后启动的线程), 而不是在 100ms 后, 当自动执行线程结束时再变成默认设置.

通过利用动态分配, 以下限制已被取消:

- 行或延续片段最大长度为 16383 个字符.
- 每个表达式最大 512 个标记(MAX_TOKENS).
  基于 MAX_TOKENS 确定大小的表达式计算器的内部数组现在是基于预先计算的所需大小的估计值, 因此性能应该是相似的, 但在大多数情况下堆栈的使用量会低一些. 这可能会增加用户定义的函数的最大递归深度.
- 每个参数最多可以引用 512 个 var 或函数(但无论如何, MAX_TOKENS 对表达式的限制更大).
- 每个函数最多可以调用 255 个指定的参数值(但无论如何 MAX_TOKENS 更有限制性).

ListVars 现在将静态变量与局部变量分开显示. 在函数中声明的全局变量也被列为静态变量(这是新的实现细节的副作用, 但被保留下来, 因为它在有许多全局变量的脚本中可能是有用的).

为了减少代码大小和维护成本, 删除了(未记录的) "lazy var". 这个优化改进了有超过 100,000 个变量的脚本的性能.

托盘菜单: 从 "Reload This Script" 和 "Edit This Script" 中删除了 "This", 以便与 "Pause Script" 和主窗口的菜单选项一致.

如果时间戳长度不是 4 到 14(包括) 之间的偶数, YYYYMMDDHH24MISS 时间戳值现在被认为是无效的.

### 持续运行

当至少满足以下条件之一时, 脚本 "持续运行":

- 至少有一个热键或热字符串被脚本定义.
- 至少有一个 Gui(或脚本的主窗口) 可见.
- 至少有一个脚本的定时器目前是启用的.
- 至少有一个 OnClipboardChange 的回调函数已经被设置.
- 至少有一个 InputHook 是激活的.
- 调用了 `Persistent()` 或 `Persistent(true)` 并且没有通过调用 `Persistent(false)` 来反转.

如果出现以下情况之一, 并且没有满足以上条件, 那么脚本就会终止.

- 最后一个脚本线程结束了.
- Gui 被关闭或销毁.
- 脚本的主窗口被关闭(但销毁它将导致脚本退出, 而不考虑持续运行, 如前所述).
- 一个没有 OnEnd 回调的 InputHook 结束.

为了灵活起见, OnMessage 并不能使脚本自动持续运行.

相比之下, 当以下情况中至少有一个为真时, v1 脚本就会 "持续运行":

- 至少有一个热键或热字串被脚本定义.
- Gui 或 OnMessage() 出现在脚本的任何地方.
- 键盘钩子或鼠标钩子已被安装.
- Input 已经被调用.
- #Persistent 已经被使用.

### 线程

线程开始时的不间断超时为 17ms, 而不是 15ms. 15 太短了, 因为系统的 tick 计数是以 15 或 16 为最小单位更新的; 也就是说, 如果 tick 计数正好在错误的时刻更新, 线程就可能成为可中断的, 尽管几乎没有时间过去.

线程在开始时是不可中断的, 现在则保持这种状态, 直到至少执行了一行, 即使不可中断的超时时间首先到期(例如, 如果系统在线程开始后立即暂停进程, 以便将 CPU 时间给另一个进程).

#MaxThreads 和 #MaxThreadsPerHotkey 不再对任何第一行是下列函数之一的子程序设置异常: ExitApp, Pause, Edit, Reload, KeyHistory, ListLines, ListVars 或 ListHotkeys.

### 默认设置

- #NoEnv 是默认行为, 所以该指令本身已经被删除. 如果等效的内置变量不可用, 请使用 EnvGet 代替.
- SendMode 默认为输入(Input) 而不是事件(Event).
- 标题匹配模式默认为 2 而不是 1.
- 删除了 SetBatchLines, 因此所有脚本都以全速运行(等同于 v1 中的 SetBatchLines -1).
- 工作目录默认为 A_ScriptDir. A_InitialWorkingDir 包含由启动 AutoHotkey 的进程设置的工作目录.
- #SingleInstance 提示行为对所有脚本来说都是默认的; #SingleInstance 本身就会激活强制模式. `#SingleInstance Prompt` 也可以显式地使用, 以达到清晰的目的, 或者覆盖之前的指令.
- CoordMode 默认为 Client(在 v1.1.05 版中加入), 而不是 Window.
- 脚本文件(但不包括 由 脚本读取的文件) 的默认编码现在是 UTF-8 而不是 ANSI(CP0). 和以前一样, 这可以通过 /CP 命令行开关来覆盖.

### 命令行

命令行参数不再存储在一个编号化的伪数组全局变量中; 应该使用全局变量 A_Args(在 v1.1.27 中添加) 来代替.

删除了 /R 和 /F 开关. 使用 /restart 和 /force 代替.

当 AutoHotkey.exe 被用来检查脚本的语法错误时, 应该使用 /validate 来代替 /iLib, 因为函数库的自动包含机制被移除.

在以下任一情况下, /ErrorStdOut 现在被视为脚本的参数之一, 而不是内置的:

- 当脚本是编译的, 除非使用了 /script.
- 当它的后缀不是以 `=` 开头时(以前后缀是被忽略的).

dftg