本文以一个扩展ahk v2内置的Array类为例,解析这个最佳实践是如何起作用的。先简单说明下这个扩展实现了哪些实用的数组函数。
数组方法完整说明(含用法解析):
1. Array.Slice(start:=1, end:=0, step:=1)
作用: 截取数组的一段元素,返回新数组(不修改原数组)
start:起始索引(默认 = 1,从第一个元素开始)
end:结束索引(默认 = 0,截取到数组末尾)
step:步长(默认 = 1,逐个截取;=2 则隔一个取一个)
示例: [1,2,3,4].Slice(2,4) → [2,3]
2. Array.Swap(a, b)
作用:交换数组中索引 a 和索引 b 位置的元素(直接修改原数组)
a:第一个元素索引
b:第二个元素索引
示例 :[1,2,3].Swap(1,3) → [3,2,1]
3. Array.Map(func)
作用 :对数组每一个元素 执行函数,返回处理后的新数组
func:自定义处理函数(输入:单个元素 → 输出:处理后的值)
示例 :[1,2,3].Map(x => x*2) → [2,4,6]
4. Array.Filter(func)
作用 :过滤数组,只保留满足条件 的元素,返回新数组
func:判断函数(返回true保留,false剔除)
示例 :[1,2,3,4].Filter(x => x>2) → [3,4]
5. Array.Reduce(func, initialValue?)
作用 :累计计算数组所有元素,最终返回一个结果
func:累计函数(前值 + 当前值 → 新值)
initialValue?:可选初始值
示例:[1,2,3].Reduce((sum, x)=>sum+x, 0) → 6(求和)
6. Array.IndexOf(value, start:=1)
作用 :查找元素第一次出现的索引 ,找不到返回 - 1
value:要查找的值
start:起始查找位置(默认 = 1)
示例 :[5,3,5].IndexOf(5) → 1
7. Array.Find(func, &match?, start:=1)
作用 :按条件查找元素,返回索引 ,并把找到的值存入match
func:条件函数
&match?:输出参数(存储找到的元素)
start:起始位置
示例 :[2,4,6].Find(x=>x>3, &num) → 返回2,num=4
8. Array.Reverse()
作用 :反转数组(直接修改原数组)
示例 :[1,2,3].Reverse() → [3,2,1]
9. Array.Count(value)
作用 :统计某个值在数组中出现的次数
示例 :[1,2,2,3].Count(2) → 2
10. Array.Sort(Key?, Options?, Callback?)
作用 :排序数组(直接修改原数组)
Key?:可选排序依据(对象数组用)
Options?:排序规则(升序 / 降序)
Callback?:自定义排序函数
示例 :[3,1,2].Sort() → [1,2,3]
11. Array.Join(delim:=",")
作用 :把数组所有元素拼接成字符串
delim:分隔符(默认逗号)
示例 :[a,b,c].Join("-") → "a-b-c"
12. Array.Shuffle()
作用 :随机打乱数组顺序(直接修改原数组)
示例 :[1,2,3].Shuffle() → [2,1,3](随机结果)
13. Array.Flat()
作用 :扁平化嵌套数组,变成一维数组
示例 :[1,[2,[3]]].Flat() → [1,2,3]
14. Array.Extend(arr)
作用 :把另一个数组的所有元素追加到当前数组末尾
示例 :[1,2].Extend([3,4]) → [1,2,3,4]
总结
- 不修改原数组 :
Slice、Map、Filter、Reduce、Join、Flat - 修改原数组 :
Swap、Reverse、Sort、Shuffle、Extend - 查询类 :
IndexOf、Find、Count - 所有方法均支持默认参数,直接调用即可快速使用
Array扩展函数库实现代码如下:
/*
Name: Array.ahk
Version 0.4.1 (02.09.24)
Created: 27.08.22
Author: Descolada
Description:
A compilation of useful array methods.
Array.Slice(start:=1, end:=0, step:=1) => Returns a section of the array from 'start' to 'end',
optionally skipping elements with 'step'.
Array.Swap(a, b) => Swaps elements at indexes a and b.
Array.Map(func, arrays*) => Applies a function to each element in the array and returns a new array.
Array.ForEach(func) => Calls a function for each element in the array.
Array.Filter(func) => Keeps only values that satisfy the provided function and returns a new array with the results.
Array.Reduce(func, initialValue?) => Applies a function cumulatively to all the values in
the array, with an optional initial value.
Array.IndexOf(value, start:=1) => Finds a value in the array and returns its index.
Array.Find(func, &match?, start:=1) => Finds a value satisfying the provided function and returns the index.
match will be set to the found value.
Array.Reverse() => Reverses the array.
Array.Count(value) => Counts the number of occurrences of a value.
Array.Sort(OptionsOrCallback?, Key?) => Sorts an array, optionally by object values.
Array.Shuffle() => Randomizes the array.
Array.Unique() => Returns a set of the values in the array
Array.Join(delim:=",") => Joins all the elements to a string using the provided delimiter.
Array.Flat() => Turns a nested array into a one-level array.
Array.Extend(enums*) => Adds the values of other arrays or enumerables to the end of this one.
*/
class Array2 {
static __New() => (Array2.base := Array.Prototype.base, Array.Prototype.base := Array2)
/**
* Returns a section of the array from 'start' to 'end', optionally skipping elements with 'step'.
* Modifies the original array.
* @param start Optional: index to start from. Default is 1.
* @param end Optional: index to end at. Can be negative. Default is 0 (includes the last element).
* @param step Optional: an integer specifying the incrementation. Default is 1.
* @returns {Array}
*/
static Slice(start:=1, end:=0, step:=1) {
len := this.Length, i := start < 1 ? len + start + 1 : start, j := Min(end < 1 ? len + end + 1 : end, len), r := [], reverse := False
if step = 0
Throw ValueError("Slice: step cannot be 0", -1)
if len = 0
return []
if i < 1
i := 1
r.Length := r.Capacity := Abs(j+1-i) // step
if step < 0 {
while i >= j {
if this.Has(i)
r[A_Index] := this[i]
i += step
}
} else {
while i <= j {
if this.Has(i)
r[A_Index] := this[i]
i += step
}
}
return r
}
/**
* Swaps elements at indexes a and b
* @param a First elements index to swap
* @param b Second elements index to swap
* @returns {Array}
*/
static Swap(a, b) {
temp := this.Has(b) ? this[b] : unset
this.Has(a) ? (this[b] := this[a]) : this.Delete(b)
IsSet(temp) ? (this[a] := temp) : this.Delete(a)
return this
}
/**
* Applies a function to each element in the array and returns a new array with the results.
* @param func The mapping function that accepts one argument.
* @param arrays Additional arrays to be accepted in the mapping function
* @returns {Array}
*/
static Map(func, arrays*) {
if !HasMethod(func)
throw ValueError("Map: func must be a function", -1)
local new := this.Clone()
for i, v in new {
bf := func.Bind(v?)
for _, vv in arrays
bf := bf.Bind(vv.Has(i) ? vv[i] : unset)
try bf := bf()
new[i] := bf
}
return new
}
/**
* Applies a function to each element in the array.
* @param func The callback function with arguments Callback(value[, index, array]).
* @returns {Array}
*/
static ForEach(func) {
if !HasMethod(func)
throw ValueError("ForEach: func must be a function", -1)
for i, v in this
func(v?, i, this)
return this
}
/**
* Keeps only values that satisfy the provided function and returns a new array with the results.
* @param func The filter function that accepts one argument.
* @returns {Array}
*/
static Filter(func) {
if !HasMethod(func)
throw ValueError("Filter: func must be a function", -1)
r := []
for v in this
if func(v?)
r.Push(v)
return r
}
/**
* Applies a function cumulatively to all the values in the array, with an optional initial value.
* @param func The function that accepts two arguments and returns one value
* @param initialValue Optional: the starting value. If omitted, the first value in the array is used.
* @returns {func return type}
* @example
* [1,2,3,4,5].Reduce((a,b) => (a+b)) ; returns 15 (the sum of all the numbers)
*/
static Reduce(func, initialValue?) {
if !HasMethod(func)
throw ValueError("Reduce: func must be a function", -1)
len := this.Length + 1
if len = 1
return initialValue ?? ""
if IsSet(initialValue)
out := initialValue, i := 0
else
out := this[1], i := 1
while ++i < len {
out := func(out?, this.Has(i) ? this[i] : unset)
}
return out
}
/**
* Finds a value in the array and returns its index.
* @param value The value to search for.
* @param start Optional: the index to start the search from, negative start reverses the search. Default is 1.
*/
static IndexOf(value?, start:=1) {
local len := this.Length, reverse := false
if !IsInteger(start)
throw ValueError("IndexOf: start value must be an integer", -1)
if start < 0
reverse := true, start := len+1+start
if start < 1 || start > len
return 0
if reverse {
++start
if IsSet(value) {
while --start > 0
if this.Has(start) && (this[start] == value)
return start
} else {
while --start > 0
if !this.Has(start)
return start
}
} else {
--start
if IsSet(value) {
while ++start <= len
if this.Has(start) && (this[start] == value)
return start
} else {
while ++start <= len
if !this.Has(start)
return start
}
}
return 0
}
/**
* Finds a value satisfying the provided function and returns its index.
* @param func The condition function that accepts one argument.
* @param match Optional: is set to the found value
* @param start Optional: the index to start the search from, negative start reverses the search. Default is 1.
* @example
* [1,2,3,4,5].Find((v) => (Mod(v,2) == 0)) ; returns 2
*/
static Find(func, &match?, start:=1) {
local reverse := false
if !HasMethod(func)
throw ValueError("Find: func must be a function", -1)
local len := this.Length
if start < 0
reverse := true, start := len+1+start
if start < 1 || start > len
return 0
if reverse {
++start
while --start > 0
if ((v := (this.Has(start) ? this[start] : unset)), func(v?))
return ((match := v ?? unset), start)
} else {
--start
while ++start <= len
if ((v := (this.Has(start) ? this[start] : unset)), func(v?))
return ((match := v ?? unset), start)
}
return 0
}
/**
* Reverses the array.
* @example
* [1,2,3].Reverse() ; returns [3,2,1]
*/
static Reverse() {
local len := this.Length + 1, max := (len // 2), i := 0
while ++i <= max
this.Swap(i, len - i)
return this
}
/**
* Counts the number of occurrences of a value
* @param value The value to count. Can also be a function.
*/
static Count(value?) {
count := 0
if !IsSet(value) {
Loop this.Length
if this.Has(A_Index)
count++
} else if HasMethod(value) {
for _, v in this
if value(v?)
count++
} else
for _, v in this
if v == value
count++
return count
}
/**
* Sorts an array, optionally by object keys
* @param OptionsOrCallback Optional: either a callback function, or one of the following:
*
* N => array is considered to consist of only numeric values. This is the default option.
* C, C1 or COn => case-sensitive sort of strings
* C0 or COff => case-insensitive sort of strings
*
* The callback function should accept two parameters elem1 and elem2 and return an integer:
* Return integer < 0 if elem1 less than elem2
* Return 0 is elem1 is equal to elem2
* Return > 0 if elem1 greater than elem2
* @param Key Optional: Omit it if you want to sort a array of primitive values (strings, numbers etc).
* If you have an array of objects, specify here the key by which contents the object will be sorted.
* @returns {Array}
*/
static Sort(optionsOrCallback:="N", key?) {
if (this.Length < 2)
return this
if HasMethod(optionsOrCallback)
compareFunc := optionsOrCallback, optionsOrCallback := ""
else {
if InStr(optionsOrCallback, "N")
compareFunc := IsSet(key) ? NumericCompareKey.Bind(key) : NumericCompare
if RegExMatch(optionsOrCallback, "i)C(?!0)|C1|COn")
compareFunc := IsSet(key) ? StringCompareKey.Bind(key,,True) : StringCompare.Bind(,,True)
if RegExMatch(optionsOrCallback, "i)C0|COff")
compareFunc := IsSet(key) ? StringCompareKey.Bind(key) : StringCompare
if InStr(optionsOrCallback, "Random")
return this.Shuffle()
if !IsSet(compareFunc)
throw ValueError("No valid options provided!", -1)
}
QuickSort(1, this.Length)
if RegExMatch(optionsOrCallback, "i)R(?!a)")
this.Reverse()
if InStr(optionsOrCallback, "U")
this := this.Unique()
return this
NumericCompare(left, right) => (left > right) - (left < right)
NumericCompareKey(key, left, right) => ((f1 := left.HasProp("__Item") ? left[key] : left.%key%), (f2 := right.HasProp("__Item") ? right[key] : right.%key%), (f1 > f2) - (f1 < f2))
StringCompare(left, right, casesense := False) => StrCompare(left "", right "", casesense)
StringCompareKey(key, left, right, casesense := False) => StrCompare((left.HasProp("__Item") ? left[key] : left.%key%) "", (right.HasProp("__Item") ? right[key] : right.%key%) "", casesense)
; In-place quicksort (Hoare-style partition with middle pivot)
QuickSort(left, right) {
i := left
j := right
pivot := this[(left + right) // 2]
while (i <= j) {
while (compareFunc(this[i], pivot) < 0)
i++
while (compareFunc(this[j], pivot) > 0)
j--
if (i <= j) {
temp := this[i], this[i] := this[j], this[j] := temp
i++, j--
}
}
if (left < j)
QuickSort(left, j)
if (i < right)
QuickSort(i, right)
}
}
/**
* Randomizes the array.
* @returns {Array}
*/
static Shuffle() {
len := this.Length
Loop len-1
this.Swap(A_index, Random(A_index, len))
return this
}
/**
* Returns a set of values in array
*/
static Unique() {
unique := Map()
for v in this
unique[v] := 1
return [unique*]
}
/**
* Joins all the elements to a string using the provided delimiter.
* @param delim Optional: the delimiter to use. Default is comma.
* @returns {String}
*/
static Join(delim:=",") {
result := ""
for v in this
result .= (v ?? "") delim
return (len := StrLen(delim)) ? SubStr(result, 1, -len) : result
}
/**
* Turns a nested array into a one-level array
* @returns {Array}
* @example
* [1,[2,[3]]].Flat() ; returns [1,2,3]
*/
static Flat() {
r := []
for v in this {
if !IsSet(v)
r.Length += 1
else if v is Array
r.Push(v.Flat()*)
else
r.Push(v)
}
return this := r
}
/**
* Adds the contents of another array to the end of this one.
* @param enums The arrays or other enumerables that are used to extend this one.
* @returns {Array}
*/
static Extend(enums*) {
for enum in enums {
if !HasMethod(enum, "__Enum")
throw ValueError("Extend: arr must be an iterable")
for _, v in enum
this.Push(v)
}
return this
}
}
🔍 代码解析
让我先查看完整的 Array.ahk 文件内容来理解这个代码的作用:
filePath: x:\AHK\V2\Descolada\AHK-v2-libraries-20260503\Lib\Array.ahk
📚 代码解析
autohotkey
static __New() => (Array2.base := Array.Prototype.base, Array.Prototype.base := Array2)
第32行是一个巧妙的原型扩展技巧,用来给 AutoHotkey v2 的原生数组添加额外方法。
🎯 工作原理
这是一个两步操作:
-
保存原基类 →
Array2.base := Array.Prototype.base- 把 Array2 的基类设置为 Array 原来的基类
- 保留原生功能不丢失
-
替换原型链 →
Array.Prototype.base := Array2- 把 Array.Prototype 的基类指向 Array2
- 现在所有数组都能访问 Array2 的方法
📊 原型链变化
修改前:
Array.Prototype → 原生 Array 基类
修改后:
Array.Prototype → Array2 → 原生 Array 基类
✨ 效果
现在所有数组都能直接使用 Array2 中定义的方法:
autohotkey
; 之前没有这些方法,现在都可以用了!
[1, 2, 3, 4, 5].Slice(2, 4) ; 切片
[1, 2, 3].Map((v) => v * 2) ; 映射
[1, 2, 3, 4].Filter((v) => v > 2) ; 过滤
[1, 2, 3].Reduce((a, b) => a + b) ; 归约
[3, 1, 4].Sort() ; 排序
[1, 2, 3].Reverse() ; 反转
🎓 为什么这样设计?
这是一种安全的猴子补丁方式:
- ✅ 不直接修改 Array.Prototype(避免冲突)
- ✅ 通过原型链继承方式扩展
- ✅ 保留所有原生功能
- ✅ 可以随时恢复原状
这是 AutoHotkey v2 中扩展内置类型的常见最佳实践!
⏰ static __New() 的运行时机
static __New() 是 AutoHotkey v2 中的静态构造函数 ,它在第一次访问该类时自动运行。
🔄 具体触发时机
对于这个 Array.ahk 库,会在以下场景触发:
1️⃣ 首次引用类名时
autohotkey
#Include <Array> ; ← 此时就会触发!
; 或者直接引用
arr := Array2 ; ← 如果是第一次引用,也会触发
2️⃣ 首次访问类的任何成员时
autohotkey
; 即使不直接用 Array2,只要用了数组的新方法也会触发
[1, 2, 3].Map((v) => v * 2) ; ← 触发 static __New()
3️⃣ 首次创建实例时
autohotkey
obj := Array2() ; ← 触发(如果之前没访问过)
📊 执行顺序示例
autohotkey
; 脚本启动
MsgBox "脚本开始" ; 1️⃣ 先执行
#Include <Array> ; 2️⃣ 包含时,首次访问 Array2 → 触发 static __New()
MsgBox "包含完成" ; 3️⃣ 后执行
arr := [1, 2, 3] ; 4️⃣ 使用数组
arr.Slice(1, 2) ; 5️⃣ 方法已经可以用了
🎯 这个库的特点
这个 Array.ahk 的设计特别巧妙:
- 只要包含这个库 ,
static __New()就会立即执行 - 执行后自动修改
Array.Prototype - 之后所有数组就都能用新方法了
💡 验证示例
你可以添加调试信息看时机:
autohotkey
class Array2 {
static __New() {
MsgBox "static __New() 运行了!"
Array2.base := Array.Prototype.base
Array.Prototype.base := Array2
}
; ... 其他方法
}
一包含这个库,就会弹出消息框!🎉