vim9class

vim9class.txt 适用于 Vim 9.1 版本。 最近更新: 2025年3月 VIM 参考手册 by Bram Moolenaar 译者: Willis Vim9 类、对象、界面、类型和枚举。 vim9-class 1. 总览 Vim9-class-overview 2. 简单类 Vim9-simple-class 3. 类变量及方法 Vim9-class-member 4. 使用抽象类 Vim9-abstract-class 5. 使用抽象界面 Vim9-using-interface 6. 更多类的细节 Vim9-class 7. 类型定义 Vim9-type 8. 枚举 Vim9-enum 9. 理据 10. 以后再做

1. 总览 Vim9-class-overview 时髦的叫法叫 "面向对象编程"。这个主题有大量的学习材料。本文档假定你已了解相关 基本概念,着重说明 Vim9 脚本提供的功能。还有一些如何有效使用这些功能有用的提 示。不能在老式 Vim 脚本和老式函数时使用 Vim9 类和对象。 最基本的项目是对象: - 对象保存状态。它包含一或多个变量,每个变量可带值。 - 对象提供函数,用于访问和操作状态。这些函数在 "对象上" 调用,这和传统的分离数 据以及操作数据的代码的方式有所区隔。 - 对象有定义良好的界面,包括有类型的成员变量和方法。 - 对象由类创建,所有这样的对象有相同的界面。运行时不能改变,界面并非动态。 对象只能由类创建。类提供了: - new() 方法,构造函数,返回属于该类的一个对象。此方法在类名上调用: MyClass.new()。 - 所有该类的对象共享的状态: 类变量 (类成员)。 - 类的层次结构,即超类和子类,继承。 界面用于指定对象的属性: - 对象可声明多个它实现的界面。 - 可以相同方式调用实现相同界面的不同对象。 类的层次结构支持单一继承。其它的需要可用界面实现。 类模型 可以任何方式构造类的模型。留心你要做什么,不要试图为现实世界建模。这有点混淆, 尤其老师在课堂上会用现实事物来解释类关系,你可能会因此认为你的模型应反映现实世 界。不需要!模型应匹配你的目的。 要记得组合 (一个对象包含另一对象) 常常优于继承 (一个对象扩展另一对象)。不要浪 费时间去找最优的类模型。或者浪费时间讨论到底正方形是一种矩形还是矩形是一种正方 形。这无关紧要。

2. 简单类 Vim9-simple-class 从简单的例子开始: 保存文本位置的类 (更有效的做法稍后会讲): class TextPosition var lnum: number var col: number def new(lnum: number, col: number) this.lnum = lnum this.col = col enddef def SetLnum(lnum: number) this.lnum = lnum enddef def SetCol(col: number) this.col = col enddef def SetPosition(lnum: number, col: number) this.lnum = lnum this.col = col enddef endclass object Object new() 方法创建此类的一个对象: var pos = TextPosition.new(1, 1) 可直接访问对象变量 "lnum" 和 "col": echo $'文本位置为 ({pos.lnum}, {pos.col})' E1317 E1327 :this 如果你有使用其它面向对象语言的经验,肯定会注意到 Vim 类定义内部的声明的对象成 员在引用时总是用 "this." 前缀。这和 Java 和 TypeScript 这些语言不同。此命名惯 例容易找到对象成员。相反地,不带 "this." 前缀的变量就是非对象变量。 E1411 在类定义的外部,访问对象的方法和变量要使用对象名,后跟句号,后跟成员: pos.lnum pos.SetCol(10) E1405 E1406 类名不能用作表达式。类名不能出现在赋值语句的左手边。 对象变量写访问 read-only-variable 现在试试直接修改对象变量: pos.lnum = 9 E1335 这会报错!这是因为缺省对象变量可以读但不可写。所以 TextPosition 类要为此提供一 个方法: pos.SetLnum(9) 对象变量可读但不可写是最常见和安全的方法。通常读值没有问题,但设置值时会有副作 用,需要特别处理。此例中,SetLnum() 方法可以检查行号是否合法,不然可以报错或者 选择最接近的合法值。 :public public-variable E1331 如果你不担心副作用而想随时修改对象变量,可标记其为 public: public var lnum: number public var col: number 现在你就不需要 SetLnum()、SetCol() 和 SetPosition() 方法了,直接设置 "pos.lnum" 不再报错。 E1326 试图设置尚不存在的对象变量会报错: pos.other = 9 E1326: Member not found on object "TextPosition": other E1376 不能使用类名来访问对象变量。 受保护变量 protected-variable E1332 E1333 另一方面,如果不想让对象变量在类或子类的外部直接可读,可标记其为受保护。这是通 过给名字加上下划线前缀完成的: var _lnum: number var _col: number 你现在需要提供读取受保护变量值的方法。常称为 getter。推荐使用 "Get" 开头: def GetLnum(): number return this._lnum enddef def GetCol(): number return this._col enddef 此例不甚有用,还不如干脆声明变量为公共。但如果可以对值进行检查,就会有用些。例 如,限制行号不超过总行数: def GetLnum(): number if this._lnum > this._lineCount return this._lineCount endif return this._lnum enddef 受保护方法 protected-method E1366 要使得对象方法只能被同类的其他方法访问,而在类外不可见,可标记其为受保护。这是 通过给方法名字加上下划线前缀完成的: class SomeClass def _Foo(): number return 10 enddef def Bar(): number return this._Foo() enddef endclass 在类的外部访问受保护方法会报错 (接上例的类定义): var a = SomeClass.new() a._Foo() 简化 new() 方法 new() constructor 另见 default-constructormultiple-constructors 。 许多构造函数接受对象变量的值作为参数。常常会见到以下模式: class SomeClass var lnum: number var col: number def new(lnum: number, col: number) this.lnum = lnum this.col = col enddef endclass E1390 这不但要写大段代码,还要重复提供每个变量的类型。因为这种模式很常见,系统提供了 以下编写 new() 的快捷方式: def new(this.lnum, this.col) enddef 这里的语义很容易理解: 以带 "this." 的对象变量名作为 new() 参数,意味着调用 new() 时提供的值赋值到该对象变量。此机制来自于 Dart 语言。 组合使用这种使用 new() 的方法和标记公共变量,会得到和最初例子等价但简短得多的 类定义: class TextPosition public var lnum: number public var col: number def new(this.lnum, this.col) enddef def SetPosition(lnum: number, col: number) this.lnum = lnum this.col = col enddef endclass 构造新对象的顺序是: 1. 分配内存,并清零内存。所有值初始为零/假值/空值。 2. 为每个声明时带初始器的的对象变量进行表达式计算并赋值。按照变量在类中定义的 顺序依次进行。 3. 对使用 "this.name" 形式的 new() 方法的参数进行赋值。 4. 执行 new() 方法的本体。 如果类扩展了某父类,进行同样的操作。在第二步中先初始父类的对象变量。无需为父类 调用 "super()" 或 "new()"。 E1365 new() 方法的定义不能指定返回类型。它总是返回类的一个对象。 new() 方法可写作 "_new()",标识其为受保护方法。这可用于支持单例设计模式。 E1386 调用对象方法时,方法名之前必须是对象变量名。不能使用类名来调用对象方法。

3. 类变量和方法 Vim9-class-member :static E1337 E1338 E1368 用 "static" 声明类成员。在类定义的内部,使用这些名字不需前缀: class OtherThing var size: number static var totalSize: number def new(this.size) totalSize += this.size enddef endclass E1340 E1341 因为按本名使用,不允许覆盖方法参数名或局部变量名。 E1374 E1375 E1384 E1385 在类定义之外,类成员的访问必须使用类名作为前缀。不能使用对象来访问类成员。 和对象成员一样,可通过下划线名字前缀来使访问受保护,也可用 "public" 前缀来标识 其为公共: class OtherThing static var total: number # 任何人都可读,只有类可写 static var _sum: number # 只有类可读写 public static var result: number # 任何人可读写 endclass class-method 也可用 "static" 声明类方法。它们可以使用类变量但不能访问对象变量,不能使用 "this" 关键字: class OtherThing var size: number static var totalSize: number # 清除总大小,返回清除之前的值。 static def ClearTotalSize(): number var prev = totalSize totalSize = 0 return prev enddef endclass 在类内部,可以直接按名字调用类方法,在类的外部则必须使用类名前缀: OtherThing.ClearTotalSize() 。另外在包括类变量的初始块、匿名函数表达式和内嵌 函数这些特殊上下文里,公共类方法也必须使用名字前缀: class OtherThing static var name: string = OtherThing.GiveName() static def GiveName(): string def DoGiveName(): string return OtherThing.NameAny() enddef return DoGiveName() enddef static def NameAny(): string return "any" enddef endclass 和对象方法一样,可用下划线作为方法名首字符使之受保护: class OtherThing static def _Foo() echo "Foo" enddef def Bar() _Foo() enddef endclass E1370 注意 构造函数不能声明为 "static"。它们的调用方式类似于类方法,但执行方式类似于 对象方法;它们可以访问 "this"。 要在扩展类里访问父类的类方法和类变量,必须使用类名前缀,就像在类的定义之外的其 他一切场合一样: vim9script class Vehicle static var nextID: number = 1000 static def GetID(): number nextID += 1 return nextID enddef endclass class Car extends Vehicle var myID: number def new() this.myID = Vehicle.GetID() enddef endclass 子类不继承类变量和方法。子类可以声明与父类重名的类变量或方法。此时,根据成员所 在的类使用相应的类成员。子类类成员的类型可与父类的不同。 类或对象方法名的双下划线 (__) 前缀为将来的使用保留。 object-final-variable E1409 可用 :final 关键字使类或对象变量为常量。例如: class A final v1 = [1, 2] # final 对象变量 public final v2 = {x: 1} # final 对象变量 static final v3 = 'abc' # final 类变量 public static final v4 = 0z10 # final 类变量 endclass final 变量只能在构造函数里修改。示例: class A final v1: list<number> def new() this.v1 = [1, 2] enddef endclass var a = A.new() echo a.v1 注意 final 变量的值可以被修改。示例: class A public final v1 = [1, 2] endclass var a = A.new() a.v1[0] = 6 # 正确 a.v1->add(3) # 正确 a.v1 = [3, 4] # 报错 E1408 界面不支持 final 变量。类或对象方法不能是 final。 object-const-variable 可用 :const 关键字使类或对象变量及其值为常量。示例: class A const v1 = [1, 2] # 常量对象变量 public const v2 = {x: 1} # 常量对象变量 static const v3 = 'abc' # 常量类变量 public static const v4 = 0z10 # 常量类变量 endclass 常量变量只能在构造函数里修改。示例: class A const v1: list<number> def new() this.v1 = [1, 2] enddef endclass var a = A.new() echo a.v1 常量变量和其值不能被修改。示例: class A public const v1 = [1, 2] endclass var a = A.new() a.v1[0] = 6 # 报错 a.v1->add(3) # 报错 a.v1 = [3, 4] # 报错 E1410 界面不支持常量变量。类或对象方法不能是常量。

4. 使用抽象类 Vim9-abstract-class 抽象类构成了至少一个的子类的基底。类模型常常需要若干类共享相同的属性,但带这些 属性的类缺乏足够的状态以创建对象。子类必须先扩展抽象类,加入缺失的状态和/或方 法,然后才能用于创建对象。 例如,Shape 类可以保存 color 和 thickness。不能直接创建 Shape 对象,因为缺少何 种形状的信息。Shape 类函数构成了 Square 和 Triangle 类的基底,后两者就可以创建 对象了。例如: abstract class Shape var color = Color.Black var thickness = 10 endclass class Square extends Shape var size: number def new(this.size) enddef endclass class Triangle extends Shape var base: number var height: number def new(this.base, this.height) enddef endclass 抽象类和普通类的定义方式相同,但不能用 new() 方法。 E1359 abstract-method E1371 E1372 抽象类中,可在方法定义时用 "abstract" 前缀,来定义抽象方法: abstract class Shape abstract def Draw() endclass 抽象类里的类方法不能是抽象方法。 E1373 扩展抽象类的非抽象类必须实现所有的抽象方法。签名 (参数、参数类型及返回类型) 必 需严格一致。如果方法的返回类型为类,扩展方法可以使用该类本身或其子类。

5. 使用界面 Vim9-using-interface 上面 Shape、Square 和 Triangle 的例子如果提供了计算对象面积的方法会更有用。为 此,我们可以创建 HasSurface 界面,指定一个返回数值的方法 Surface()。本例在上例 基础上进行扩展: abstract class Shape var color = Color.Black var thickness = 10 endclass interface HasSurface def Surface(): number endinterface class Square extends Shape implements HasSurface var size: number def new(this.size) enddef def Surface(): number return this.size * this.size enddef endclass class Triangle extends Shape implements HasSurface var base: number var height: number def new(this.base, this.height) enddef def Surface(): number return this.base * this.height / 2 enddef endclass E1348 E1349 E1367 E1382 E1383 如果类声明会实现某界面,类中必须出现该界面的所有项目,且类型相同。 界面名可用做类型: var shapes: list<HasSurface> = [ Square.new(12), Triangle.new(8, 15), ] for shape in shapes echo $'面积是 {shape.Surface()}' endfor E1378 E1379 E1380 E1387 界面只能包含对象方法和只读的对象变量。界面不能包含可读写和受保护的对象变量、受 保护的对象方法、类变量和类方法。 界面可用 "extends" 扩展另一个界面。子界面继承父界面的所有实例变量和方法。

6. 更多类的细节 Vim9-class Class class 定义类 :class :endclass :abstract 类定义出现在 :class:endclass 之间。整个类在一个脚本文件中定义。定义之 后不能为类新增内容。 只能在一个 Vim9 脚本文件中定义类。 E1316 不能在函数内部定义类。 E1429 一个脚本文件中可以定义多个类。最好只导出一个主类。不过,定义类型、枚举和帮助类 还是常用的。 可有前缀 :abstract 关键字,也可用 :export 。这样就有以下组合: class ClassName endclass export class ClassName endclass abstract class ClassName endclass export abstract class ClassName endclass E1314 类名应使用驼峰式。必须以大写字母开头。这样可以避免和内建类型冲突。 E1315 类名之后可选若干项目。每种只能出现一次。可以任何顺序出现,但建议以下顺序: extends ClassName implements InterfaceName, OtherInterface specifies SomeInterface "specifies" 功能目前尚未实现。 E1355 E1369 每个变量和函数名只能用一次。不能定义同名但带不同类型参数的方法。不能使用同名的 公有和受保护成员变量。父类使用的对象变量名不能被子类重用。 对象变量初始化 如果在类里没有显式指定变量类型,那么类定义时会将其设为 "any"。此类在实例化对象 时,设置变量的类型。 下列保留字不可用作对象或类变量名: "super"、"this"、"true"、"false"、"null"、 "null_blob"、"null_dict"、"null_function"、"null_list"、"null_partial"、 "null_string"、"null_channel" 和 "null_job"。 类扩展 extends 类可以扩展其它类。 E1352 E1353 E1354 基本的想法是在已有的类上加上新的属性。 被扩展类称为 "基类" 或 "超类"。新类称为 "子类"。 基类的对象变量被子类全盘接收。不可覆盖 (和一些其它的语言不同)。 E1356 E1357 E1358 基类的对象方法可以被覆盖。签名 (参数、参数类型和返回类型) 必须完全一致。如果方 法的返回类型是类,扩展方法可以使用该类本身或其子类。可通过 "super." 前缀调用基 类提供的方法。 E1377 子类方法的访问域 (公共或受保护) 必须与父类的相同。 其它对象方法被子类全盘接收。 类方法,包含 "new" 开始的函数,和对象方法一样可以覆盖。可通过类名 (用于类方法) 前缀或 "super." 前缀调用基类的方法。 和其它语言不同,不需要调用基类的构造函数。事实上,也不可以调用。如果子类需要基 类执行的某种初始化,把相关逻辑放在一个对象方法里,在每个构造函数里调用该方法。 如果基类不提供 new() 方法,会自动创建一个。此方法不被子类接收。子类可以定义自 己的 new() 方法,同样地,如果没有,会自动创建一个。 实现界面的类 implements E1346 E1347 E1389 类可以实现一个或多个界面。"implements" 关键字只能出现一次 E1350 。 可指定多个界面,以逗号分隔。每个界面名只能出现一次。 E1351 定义界面的类 specifies 类可以通过一个命名的界面来声明自己的界面、对象变量和方法。这样就不需要单独指定 界面了,很多语言,尤其是 Java,就是如此。 TODO: 功能目前尚未实现。 类中的项目 E1318 E1325 E1388 类内部,即 :class:endclass 之间,可出现以下项目: - 对象变量声明: var _protectedVariableName: memberType var readonlyVariableName: memberType public var readwriteVariableName: memberType - 类变量声明: static var _protectedClassVariableName: memberType static var readonlyClassVariableName: memberType public static var readwriteClassVariableName: memberType - 构造方法: def new(arguments) def newName(arguments) - 类方法: static def SomeMethod(arguments) static def _ProtectedMethod(arguments) - 对象方法: def SomeMethod(arguments) def _ProtectedMethod(arguments) 对象变量必须指定类型。最好的方法是显式指定 ": {type}"。简单类型也可用初始器, 形如 "= 123",Vim 可以推定类型为数值。更复杂的类型不要这样做,也不能用于不完整 的类型。如: var nameList = [] 这里指定了列表,但项目类型未知。最好这样: var nameList: list<string> 这里不需要初始化,列表缺省已为空。 E1330 有些类型不能使用,如 "void"、"null" 和 "v:none"。 内建对象方法 builtin-object-methods 有些内建函数,如 empty()len()string() 可用于任何对象。对象可实现和 这些内建函数同名的方法,以返回对象特定的值。 E1412 支持下面的内建方法: object-empty() empty() 被 empty() 函数调用,以检查对象是否为空。如果此方法缺失,返回 true。此方法不应接受任何参数,必须返回布尔型。 object-len() len() 被 len() 函数调用,以返回对象的长度。如果类中缺失此方法,报错且 返回零。此方法不应接受任何参数,必须返回数值。 object-string() string() 被 string() 函数调用,以返回对象的文本表示。 :echo 命令作用于 对象时也会调用此方法。如果类中缺失此方法,返回内建的文本表示。 此方法不应接受任何参数,必须返回字符串。 E1413 类方法不能用作内建方法。 定义界面 Interface :interface :endinterface 界面在 :interface:endinterface 间定义。可加上前缀 :export : interface InterfaceName endinterface export interface InterfaceName endinterface E1344 界面可声明对象变量,和类一样,但没有初始器。 E1345 界面可用 :def 声明方法,包括参数和返回类型,但没有本体,也没有 :enddef 。示 例: interface HasSurface var size: number def Surface(): number endinterface 界面名必须以大写字母开始。 E1343 "Has" 前缀方便判断这是界面名,并提示了它提供的功能。 只能在 Vim9 脚本文件中定义界面。 E1342 界面不能 "implement" 另一个界面,但可以 "extend" 另一个界面。 E1381 null 对象 变量声明时如果指定对象的类型但没有初始化时,其值为 null。试图使用 null 对象时, Vim 经常不知道应该使用哪个类。因而,Vim 无法检查变量名是否正确,从而给出 "Using a null object" 错误,即使变量名是非法的。 E1360 E1362 缺省构造函数 default-constructor 如果定义类时未提供 new() 方法,会自动生成一个。此缺省构造函数会为每个对象变量 按照它们出现的顺序分别提供一个参数。如你的类是这样: class AutoNew var name: string var age: number var gender: Gender endclass 那么缺省构造函数就会是: def new(this.name = v:none, this.age = v:none, this.gender = v:none) enddef 缺省值 "= v:none" 使参数可选。所以,也可不带任何参数地调用 new() 。不赋值而使 用对象变量的缺省值。下面是更有用的带缺省值的例子: class TextPosition var lnum: number = 1 var col: number = 1 endclass 如果构造函数要强制提供参数,必须自己编写。例如,要上面的 AutoNew 类必须提供 name,可这样定义构造函数: def new(this.name, this.age = v:none, this.gender = v:none) enddef 如果使用缺省 new() 方法而后来改变了类里对象变量的顺序,所有调用缺省 new() 方法 的调用者并须要跟着改变。为了避免这一点,显式定义 mew() 方法且不带参数。 E1328 注意 除了这里的 "v:none" 之外,不能使用其它缺省值。要初始化对象变量,在其声明 的地方提供初始值。这样你只须在一个地方寻找缺省值。 缺省构造函数中使用所有的对象变量,包括受保护访问的那些。 如果类扩展其它类,被扩展类的对象变量会先出现。 多个构造函数 multiple-constructors 通常每个类只有一个 new() 构造函数。如果常常需要传递相同的参数,可简化代码,把 这些参数放到另一个构造函数里。例如,如果经常使用黑色: def new(this.garment, this.color, this.size) enddef ... var pants = new(Garment.pants, Color.black, "XL") var shirt = new(Garment.shirt, Color.black, "XL") var shoes = new(Garment.shoes, Color.black, "45") 要避免每次重复相同的颜色,可加入另一构造函数,包含此颜色: def newBlack(this.garment, this.size) this.color = Color.black enddef ... var pants = newBlack(Garment.pants, "XL") var shirt = newBlack(Garment.shirt, "XL") var shoes = newBlack(Garment.shoes, "9.5") 注意 此方法名必须以 "new" 开始。如果没有叫 "new()" 的方法,会加入缺省构造函 数,即使有其它的构造函数也是如此。 变量类型 "any" 用于对象 obj-var-type-any 变量声明可用类型 "any" 来保存任何对象。如. vim9script class A var n = 10 def Get(): number return this.n enddef endclass def Fn(o: any) echo o.n echo o.Get() enddef var a = A.new() Fn(a) 此例中,Vim 不能在编译时决定函数 Fn() 里的参数 "o" 的类型。它可以是 DictObject 值。所以在运行时,类型已知,查找对象的成员变量和方法。此过程效率不 高,所以要更有效率,如有可能,建议指定更专门的类型。 编译类中的方法 class-compile :defcompile 命令可用于编译类中定义的所有类和对象方法: defcompile MyClass # 编译类 "MyClass" defcompile # 编译当前脚本里的所有类

7. 类型定义 typealias Vim9-type :type E1393 E1395 E1396 E1397 E1398 类型定义为类型规格给定名称。这也被称作 "类型别名"。所有使用内建类型的地方都可 以使用类型别名。例如: type ListOfStrings = list<string> var s: ListOfStrings = ['a', 'b'] def ProcessStr(str: ListOfStrings): ListOfStrings return str enddef echo ProcessStr(s) E1394 类型别名的名字必须以大写字母开头。只能别名已有的类型。 E1399 创建的类型别名只作用在脚本层级,而不能局限在函数内部。可导出类型别名,使之可跨 脚本应用。 E1400 E1401 E1402 E1403 E1407 类型别名不能用作表达式。类型别名不能出现在赋值语句的左手边。 typename() 函数返回类型别名原来的类型: type ListOfStudents = list<dict<any>> echo typename(ListOfStudents) typealias<list<dict<any>>>

8. 枚举 Vim9-enum :enum :endenum enum E1418 E1419 E1420 枚举是从给定值列表中选取一个的类型。示例: enum Color White, Red, Green, Blue, Black endenum enumvalue E1422 枚举值以逗号分隔。一行内可列出多于一个枚举值。最后一个枚举值后跟不跟逗号。 枚举值的访问使用枚举名后跟值名: var a: Color = Color.Blue 枚举视作类,每个枚举值相当于类的一个实例。和通常用 new() 实例化对象不同,枚 举补全不能如此创建。 只能在 Vim9 脚本文件里定义枚举。 E1414 不能在函数内部定义枚举。 E1415 枚举名必须以大写字母开头。枚举里的枚举值名可以大写或小写字母开头。 E1416 枚举可实现界面,但不能扩展类: enum MyEnum implements MyIntf Value1, Value2 def SomeMethod() enddef endenum enum-constructor 枚举里,枚举值对象的构造和其他对象用 new() 方法构造一样。参数可传递进枚举的 构造函数,方法是像调用函数一样,在枚举值名后指定参数。缺省构造函数不带参数。 E1417 枚举可包含类变量、类方法、对象变量和对象方法。枚举的方法不能是 :abstract 方 法。 下例展示了带对象变量和方法的枚举: vim9script enum Planet Earth(1, false), Jupiter(95, true), Saturn(146, true) var moons: number var has_rings: bool def GetMoons(): number return this.moons enddef endenum echo Planet.Jupiter.GetMoons() echo Planet.Earth.has_rings E1421 E1423 E1424 E1425 枚举及其值不能修改。不能用作数值或字符串类型。枚举值可声明能修改的实例变量。 enum-name 每个枚举值对象有个 "name" 实例变量,包含枚举值的名字。这是只读变量。 enum-ordinal E1426 每个枚举值有个相关联的序数,从 0 开始。枚举值的序数可用 "ordinal" 实例变量访 问。这是只读变量。注意 如果枚举里的枚举值的顺序被改变,序数值也会跟着改变。 enum-values 可用类变量 "values" 访问枚举里的所有值,一个包含枚举对象的列表。这是只读变量。 示例: enum Planet Mercury, Venus, Earth endenum echo Planet.Mercury echo Planet.Venus.name echo Planet.Venus.ordinal for p in Planet.values # ... endfor 枚举是一个类,它带有包含枚举值对象的类变量,也带有给出枚举值名和枚举值序数的对 象变量: enum Planet Mercury, Venus endenum 上述枚举定义等价于下面的类定义: class Planet public static final Mercury: Planet = Planet.new('Mercury', 0) public static final Venus: Planet = Planet.new('Venus', 1) public static const values: list<Planet> = [Planet.Mercury, Planet.Venus] public const name: string public const ordinal: number endclass

9. 理据 Vim9 类的绝大多数选择来自流行和新开发的语言,如 Java、TypeScript 和 Dart。语 法按照 Vim 脚本的工作方式进行了调整,如使用 endclass 而不是用花括号包围整个 类。 面向对象语言的很多常用构造是多年前此类型的编程方法尚在幼年时形成的,后来发现并 非理想。但这些构造已广为使用,无法再改变。而 Vim 完全有做出不同选择的自由,因 为类是全新的功能。和 "老" 语言相较,我们可以使语法更简单也更统一。变化不宜太 大,跟你熟悉的已有语言仍然要大体相像。 一些近期开发的语言加入了很多花哨的功能,我们 Vim 不需要。但有些好的想法我们也 想采用。这样,我们最终的选择以常用语言的共同语法为基底,删除不好的功能,加入新 的容易理解且相当不错的功能。 我们做决定的主要原则是: - 尽量简单。 - 没有惊奇,和其它语言做的大体相同。 - 避免过去的错误。 - 脚本作者不需要查询帮助就可以理解如何工作,绝大多数语法应一目了然。 - 保持统一。 - 服务目标是一般大小的插件,而不是巨型项目。 构造函数使用 new() 许多语言的构造函数使用类名。缺点是此名字常常很长。而且类改名时构造函数也要跟着 改名。不是大问题,但总是个缺点。 其它语言,如 TypeScript,使用特定名字,如 "constructor()",这会好一些。不过使 用 "new" 或 "new()" 来创建新对象和 "constructor()" 好象关系不大。 对 Vim9 脚本而言,为所有的构造函数使用相同的方法名看来是正确的选择,而叫它 new() 使调用者和被调用的方法之间的关系非常明确。 构造函数不能重载 Vim 脚本里,不管是老式还是 Vim9 脚本,方法不能重载。这意味着不能有相同的方法 名但带不同类型的参数。因此,只能有一个 new() 构造函数。 Vim9 脚本使重载成为可能,因为参数有类型。但这很快变得复杂。看到 new() 调用 时,要先检查参数的类型,以确定多个 new() 方法里实际要调用哪个。而这需要检视相 当多的代码。如其中一个参数是方法的返回值,那么就要先找到该方法,来查看它返回什 么类型。 相反地,每个构造函数必须有以 "new" 开始的不同名字。这样就可以有带不同参数的多 个构造函数,也容易看到使用的是哪个构造函数。也可以进行适当地参数类型检查。 方法不能重载 和构造函数同样的原因: 参数类型不总是显而易见的,要判定实际调用的是哪个方法因而 常有困难。最好给方法不同的名字,这样类型检查会如你所愿。这样会排除多态性,但我 们本来就不怎么需要。 单一继承和界面 一些语言支持多重继承。有些情况这很有用,但类如何工作的规则因而非常复杂。相反 地,使用界面来声明支持何种特性就简单得多。非常流行的 Java 语言就是这么做的,对 Vim 也应该够用了。"尽量简单" 原则在此适用。 显式声明类支持某个界面,会容易看清类的设计用途。这样也可适当地进行类型检查。界 面改变时,也会检查任何声明实现了该界面的类,确定已作了相应的修改。因为类有某界 面的方法就假定它实现了该界面的机制是很脆弱的,会导致微妙的问题,我们不这么做。 在所有地方使用 "this.variable" 不同编程语言中的对象变量有不同的访问方式,取决于出现的位置。有时需要 "this." 前缀避免歧义。通常的声明可以没有 "this."。这很不一致,有时会引起混淆。 一个常见的问题是构造函数里参数名和对象变量名相同。在函数本体里访问这些变量需要 "this." 前缀,而访问其它变量却不需要,常常省略。这造成了带 "this." 的变量和不 带的混用,非常不统一。 Vim9 类里总是为声明的方法和变量使用 "this." 前缀。简单且统一。阅读类内部的代 码时,可以直接看清哪些变量引用指向对象变量,哪些不是。 使用类变量 使用 "static variable" 来声明类变量很常见,没什么新奇的。 Vim9 脚本里类变量可 通过名字直接访问。就像方法里使用脚本局部变量一样。因为对象变量访问时必须带上 "this." 前缀,是何种变量可快速判断。 TypeScript 在类变量名前加上类名前缀,即使在类内部也是如此。这有两个问题: 类名 可能颇长,会占据相当空间,而且类改名时这些地方也要跟着改名。 声明对象及类变量 我们面临的主要选择是变量声明是否使用 "var"。 TypeScript 不用: class Point { x: number; y = 0; } 仿照此风格,Vim 对象变量可以这样声明: class Point this.x: number this.y = 0 endclass 有些用户指出,这看来更像是赋值而不是声明。加上 "var" 并省略 "this." 改变了这一 点: class Point var x: number var y = 0 endclass 为此,也需要用 "static" 关键字来声明类变量。这时可以选择省略 "var": class Point var x: number static count = 0 endclass 用的话,可以放在 "static" 之前: class Point var x: number var static count = 0 endclass 也可以放在 "static" 之后: class Point var x: number static var count = 0 endclass 这与 "static def Func()" 更一致些。 是否使用 "var" 没有明显优劣。不用的两个主要原因是: 1. TypeScript 和其它流行语言不用。 2. 少点拥挤。 不过,更常见的是语言把一般的变量和函数声明语法重用于类/对象的变量和方法上。 Vim9 也把一般的函数声明语法重用于方法上。因此,为了统一起见,我们要求以上声明 使用 "var"。 用 "ClassName.new()" 来构造对象 许多语言使用 "new" 操作符来创建对象,这有点奇怪,因为构造函数定义为带参数的方 法,而不是命令。TypeScript 也有 "new" 关键字,但方法名却是 "constructor()",很 难看清两者的关联。 Vim9 脚本里构造方法叫做 new(),也使用 new() 调用,简单直接。其它语言使用 "new ClassName()" 而没有 ClassName() 方法,反而是名为 ClassName 的类里带其它名 字的方法。很混淆。 Vim9class 访问模式 vim9-access-modes Vim9class 支持的变量访问方法及其意义是 public-variable 可在任何地方读写 read-only-variable 可在任何地方读,只能在类及其子类内部写 protected-variable 只能在类及其子类内部读写 方法的访问模式类似,但没有只读模式。 对象变量缺省读访问 许多用户评论道对象变量的访问规则不对称。好吧,这是有意的。修改值和读取值是很不 同的操作。读操作没有副作用,可以进行任何次数而不影响对象本身。修改值则可能有很 多副作用,甚至会有连锁反应,影响到其它对象。 新增对象变量时,人们一般想不到这么多,类型对就够了。值通常在 new() 方法里赋 值。所以缺省读访问在绝大多数情况下 "应该就很好"。直接修改时会有报错,提醒你是 不是真地要这么干。写代码时这可以帮助少点错误。 加下划线使对象变量受保护 对象变量受保护时,只可在类 (和子类) 中读取和修改,不能在类外使用。前加下划线是 这么做的简单办法。若干编程语言建议如此。 如果要改变想法,使对象变量在类外也可访问,只要在所有的地方删除下划线。因为名字 只出现在类 (和子类) 中,应该容易找到和修改。 反过来就难很多: 可以简单地在对象变量前加上下划线使之受保护,但所有其它地方你要 自己追踪到和修改。可能要提供 "set" 方法调用。这也反映了现实中的问题,拿走权限 需要在所有使用该权限的地方下功夫。 替代方案会用 "protected" 关键字,就像 "public" 在反方向修改访问那样。好吧,不 这么做只是为了减少关键字数量。 没有私有对象变量 有些语言提供了若干方法控制对象变量的访问。最有名的是 "protected",其意义随语言 有所不同。其它的还有 "shared"、"private"、"package" 甚至 "friend"。 这些规则使生活变麻烦。在许多人在同一个复杂的代码上操作的项目里,容易出错而这些 规则可能有其合理性。尤其是重整或其它类模型上的修改。 Vim 脚本目标是为了插件设计,编写者只有一个人或小团队。复杂的规则只会让事情更复 杂,规则提供的额外安全性并非必要。我们还是简单点,不要去指定访问的细节了。

10. 以后再做 newSomething() 构造函数可以调用另一个构造函数吗?如果是的话,有什么限制? 想法: - 类的泛型: `class <Tkey, Tentry>` - 函数的泛型: `def <Tkey> GetLast(key: Tkey)` - 织入: 不确定有没有用,为简单先不列入。 看来是好的新增特性: - 用于测试: Mock 机制 一种将来会提供的重要的类是 "Promise"。因为 Vim 是单线程的,和异步操作连接是允 许插件实现非用户阻塞式功能的自然方式。这将是调用回调和处理超时以及错误的统一方 法。 vim:tw=78:ts=8:noet:ft=help:norl: