;----------------------------------
; AHK的对象和类学习心得 By FeiYue
;----------------------------------
一、简单对象的使用(把对象当作筐,各种数据往里装,用于读写值)
AHK-V1:
简单数组 arr:=[111, 222]
关联数组 arr:={x:111, y:222, id:"abc"}
这两种可以用 arr[1] 或 arr.x 中括号或点来混合读写
AHK-V2:
简单数组 arr:=[111, 222]
关联数组 arr:=Map("x",111, "y",222, "id","abc")
原始对象 arr:={x:111, y:222, id:"abc"}
前两种应该用 arr[1]、arr["x"] 中括号来读写
第三种应该用 arr.x 点来读写,不能混用
二、类的使用(复杂对象,封装数据和方法,可以继承类,可以多态重写方法)
AHK的继承机制类似Lua和JS,是基于原型的,原型是一个普通的父对象。
原型继承的原理很简单,就是找不到就找原型对象,再找不到就再找原型对象。
****************************************
Lua的继承原理:
* 继承链:
——>子表中找不到属性或方法
——>设置元表:setmetatable(子表, 元表对象={__index=父表}),
元表对象是过渡对象,可以使用下面的技巧省去过渡对象,
父表.__index=父表, setmetatable(子表, 父表)
——>在元表对象的__index属性指向的父表中找,如果找不到
——>设置元表:setmetatable(父表, 元表对象={__index=祖父表})
如果把父表看做类,那么继承的是类的本身对象。
****************************************
AHK-V1的继承原理(类似Lua):
* 继承链:
——>子对象中找不到属性或方法
——>设置基对象:(一般通过new自动设置)子对象.base:=父对象
——>再在父对象中找,先考虑其中的元函数__Get没有明确return,如果找不到
——>设置基对象:父对象.base:=祖父对象
如果把父对象看做类,那么继承的是类的本身对象。
* new的处理:子对象:=new 父对象() (这里的父对象一般为类)
——>设置基对象:子对象.base:=父对象
——>父对象的非静态变量赋值给子对象,并运行__new()方法初始化子对象
父对象的静态变量可以被父对象本身使用,非静态变量不能。静态变量可以被实例继承。
* extends的处理:class 子类 extends 父类
——>子类.base:=父类
; AHK-V1 定义类的形式
class ClassName extends BaseClassName
{
InstanceVar := 表达式 ; 实例变量(实例属性)
static ClassVar := 表达式 ; 静态变量(类属性)
class NestedClass ; 嵌套类
{
}
Method() ; 方法, 类定义中的函数称作方法
{
}
Property[] ; 属性定义, 方括号是可选的
{
get {
return ...
}
set {
return ... := value ; value 在此处为关键字, 不可用其他名称
}
}
; 下面是元函数,属于语法糖
__Get([Key, Key2, ...])
{
}
__Set([Key, Key2, ...], Value)
{
}
__Call(Name [, Params...])
{
}
__New(args*)
{
}
__Delete()
{
}
__init() ; new 时自动生成的,把 this.InstanceVar:=表达式 放入其中
{
}
}
对于这个 Class 语法理解如下:
ClassName:={} ; 类对象本身,也作为本类的原型对象用于下级继承
ClassName.__Class:="ClassName" ; 自动添加了__Class属性
ClassName.NestedClass:={......} ; 嵌套类,可以被实例对象看做属性访问
ClassName.ClassVar := Expression ; 静态变量
ClassName.Method := function (this){} ; 类中的方法相当于属性值为函数对象
; 动态属性类似元函数,获取值调用get,设置值调用set,相当于属性值为元函数表
ClassName.Property := { get:function (this){}, set:function (this){} }
; 处理未知属性的元函数,获取值用__Get,设置值用__Set,运行方法用__Call
ClassName.__Get := function (this){}
ClassName.__Set := function (this){}
ClassName.__Call := function (this){}
; 生成实例的元函数,生成过程是新建 a:={},然后 a.base:=ClassName,
; 运行__Init方法设置 this.InstanceVar := Expression,非静态变量都保留在实例中
; 而不保留在类对象中,再运行__New获取输入的参数值
ClassName.__New := function (this){}
; 删除实例的元函数,实例的所有引用都取消后会自动运行__Delete
ClassName.__Delete := function (this){}
ClassName.base:=BaseClassName ; 继承父类,放在最后避免影响自有属性
****************************************
JS的继承原理:
* 继承链:
——>子对象中找不到属性或方法
——>设置原型:(一般通过new自动设置)子对象.__proto__=父对象.prototype
——>在父对象.prototype属性对应的对象中找,如果找不到
——>设置原型:父对象.prototype.__proto__=祖父对象.prototype
如果把父对象看做构造函数,那么继承的是构造函数的prototype属性对应的对象。
* new的处理:子对象=new 父对象() (这里的父对象一般为构造函数)
——>父对象的属性和方法初始化到 父对象.prototype 属性对应的对象中
,并运行constructor()方法初始化子对象
——>设置原型:子对象.__proto__=父对象.prototype
* extends的处理:class 子对象 extends 父对象
——>设置原型:子对象.Prototype.__proto__=父对象.Prototype
****************************************
AHK-V2的继承原理(类似JS):
* 继承链:
——>子对象中找不到属性或方法
——>设置基对象:(一般通过new自动设置)子对象.base:=父对象.Prototype
——>再在父对象.Prototype属性对应的对象中找,
先考虑其中的元函数__Get没有明确return,如果找不到
——>设置基对象:父对象.Prototype.base:=祖父对象.Prototype
如果把父对象看做类,那么继承的是类的Prototype属性对应的对象。
* new的处理:子对象:=父对象() (这里的父对象一般为类)
——>父对象的非静态属性和非静态方法初始化到 父对象.Prototype 属性
对应的对象中,并运行__new()方法初始化子对象
——>设置基对象:子对象.base:=父对象.Prototype
父对象的静态属性和静态方法、嵌套类不被子对象继承,由父对象本身使用。
* extends的处理:class 子类 extends 父类
——>设置基对象:子类.base:=父类(用于子类操作父类的静态属性和静态方法)
——>设置基对象:子类.Prototype.base:=父类.Prototype(用于实例对象继承)
; AHK-V2 定义类的形式
class ClassName extends BaseClassName
{
InstanceVar := 表达式 ; 实例变量(实例属性,不写入原型对象)
static ClassVar := 表达式 ; 静态变量(类属性)
class NestedClass ; 内置类,只能被类本身调用
{
}
Method() ; 非静态方法写入原型对象 ClassName.Prototype
{
}
static Method() ; 静态方法属于类本身
{
}
Property[Parameters] ; 仅在有参数时使用方括号,动态属性写入原型对象
{
get {
return 属性的值
}
set {
存储或以其他方式处理 值
}
}
ShortProperty ; 动态属性使用胖箭头
{
get => 计算属性值的表达式
set => 存储或以其他方式处理 值 的表达式
}
ShorterProperty => 计算属性值的表达式 ; 动态属性只用get返回值
; 下面是元函数,属于语法糖,都写入原型对象
__Get(Name, Params)
{
}
__Set(Name, Params, Value)
{
}
__Call(Name, Params)
{
}
__New(args*)
{
}
__Delete()
{
}
__init() ; new 时自动生成的,把 this.InstanceVar:=表达式 放入其中
{
}
}
对于这个 Class 语法理解如下:
ClassName:={} ; 类对象本身
ClassName.Prototype:={} ; 本类的原型对象用于下级继承
ClassName.Prototype.__Class:="ClassName" ; 自动添加了__Class属性
ClassName.NestedClass:={......} ; 内置类,只能被类本身调用
ClassName.ClassVar := Expression ; 静态变量
ClassName.DefineProp("Method", {call:Method}) ; 静态方法
ClassName.Prototype.DefineProp("Method", {call:Method}) ; 非静态方法
; 动态属性类似元函数,获取值调用get,设置值调用set,相当于属性值为元函数表
ClassName.Prototype.DefineProp("Property", {get:get, set:set})
; 处理未知属性的元函数,获取值用__Get,设置值用__Set,运行方法用__Call
ClassName.Prototype.DefineProp("__Get", {call:__Get})
ClassName.Prototype.DefineProp("__Set", {call:__Set})
ClassName.Prototype.DefineProp("__Call", {call:__Call})
; 生成实例的元函数,生成过程是新建 a:={},然后 a.base:=ClassName.Prototype,
; 运行__Init方法设置 this.InstanceVar := Expression,非静态变量都保留在实例中
; 而不保留在类对象中,再运行__New获取输入的参数值
ClassName.Prototype.DefineProp("__New", {call:__New})
; 删除实例的元函数,实例的所有引用都取消后会自动运行__Delete
ClassName.Prototype.DefineProp("__Delete", {call:__Delete})
ClassName.base:=BaseClassName, ; 继承父类,放在最后避免影响自有属性
ClassName.Prototype.base:=BaseClassName.Prototype ; 本类原型继承父类原型
三、AHK-V2类的一些补充
1、Lua的函数是一等公民,可以动态定义、赋值给变量、作为参数传递、作为函数的返回值
以及存储在数据结构中。Lua的数据结构只有表,属性和方法都是表项目,可以用For遍历。
AHK-V1的函数虽然不是一等公民,不能动态定义,但是类中定义的函数可以看做属性
的值为函数对象存储在表中。AHK对象的数据结构实际上也是表,与Lua类似,其自有的
属性和方法都是表项目,V1可以用For遍历,V2只能用OwnProps()遍历表的非动态属性。
AHK-V2的胖箭头、嵌套函数和闭包可以在表达式中动态定义函数了,很接近一等公民了,
但是胖箭头函数还不能实现循环,所以还不是完善的一等公民。
2、DefineProp很强大,可以定义自有的动态属性、方法、值属性,绕过元函数和动态属性。
Obj.DefineProp(Key, { get:func, set:func, call:func, value:value })
对象已有的(可能是自有的或继承的)动态属性和方法,不能通过赋值的方式修改,
只能用DefineProp修改为新的动态属性、方法、值属性,定义值属性等效于赋值。
3、base是继承来的动态属性,所以手动赋值也不会保存在实例的自有属性中,
如果使用 Obj.DefineProp("base", {value:1}) 设置了实例的自有base属性
覆盖了继承的动态属性,那么base属性就无特殊效果了,原继承关系不变。
4、MyObject.Property[Parameters] := Value 属性定义不带参数时V2视为数组,与
(MyObject.Property)[Parameters] := Value 等效,而V1则视为带参数的属性。
5、中括号[]的语法糖,专门给了__Item属性,要把中括号视为 对象.__Item 属性的项目。
AHK的对象和类学习心得
于 2024-07-03 23:40:06 首次发布