AutoHotkey v2 中扩展内置类型的最佳实践

本文以一个扩展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]

总结

  1. 不修改原数组Slice、Map、Filter、Reduce、Join、Flat
  2. 修改原数组Swap、Reverse、Sort、Shuffle、Extend
  3. 查询类IndexOf、Find、Count
  4. 所有方法均支持默认参数,直接调用即可快速使用

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 的原生数组添加额外方法。

🎯 工作原理

这是一个两步操作

  1. 保存原基类Array2.base := Array.Prototype.base

    • 把 Array2 的基类设置为 Array 原来的基类
    • 保留原生功能不丢失
  2. 替换原型链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
    }
    ; ... 其他方法
}

一包含这个库,就会弹出消息框!🎉

相关推荐
dingdingfish3 个月前
Bash学习 - 第6章:Bash Features,第7节:Arrays
bash·shell·array·index·associate
七夜zippoe3 个月前
Dask:超越内存限制的并行计算——从任务图到分布式调度的实战指南
python·集群·task·array·dataframe·dask
故事不长丨4 个月前
C#数组深度解析:从基础语法到实战应用
开发语言·c#·数组·array
liuyukuan4 个月前
[AHK]正在使用 Autohotkey v2
ahk·autohotkey
千里马-horse4 个月前
Napi::Array
开发语言·array·napi
Fcy6485 个月前
C++ 模版(进阶)(含array解析)
开发语言·c++·stl·array·模版
alwaysrun6 个月前
Rust中数组简介
rust·数组·array·切片
課代表6 个月前
JavaScript 中获取二维数组最大值
javascript·max·数组·递归·array·最大值·二维