一、前言

太阳编辑器功能强大,除了不能直接像WE那样直接编辑地形,物编、触发、文件导入的功能的功能都是非常的方便。

例如用xlsx批量管理(编辑)物编,JASS转TS,TS编辑触发,UI编辑器等等。

正如太阳编辑器宣传那样——“作图时间:太阳rpg编辑器(40%) + VSCode/WebStorm(40%) + WPS(10%) + WE(10%)”,所以太阳编辑器的最大优势就是:缩短地图编辑时间、增强触发在地图间移植。

这篇笔记主要按照魔兽编辑器触发讲讲TypeScript。

如果你暂时不敢兴趣可以跳过,先去看看其他感兴趣的内容。

如果你想用TS做图或者JASS转TS,那么就必须学点TypeScript编程语言基础。

太阳编辑器的开发者建议大家去菜鸟教程(www.runoob.com)学习:基础类型、变量声明、类、对象。


二、基础类型

TypeScript的基础类型相当于魔兽编辑器的变量类型。
TypeScript包含的数据类型有any、字符串、布尔类型、数组、元组、枚举、void等等。
而魔兽触发编辑器里的变量类型除了布尔类型、整数、实数、字符串,还有物品、单位、玩家、点、区域、对话框、计时器、多面板等等。

TypeScript 是 JavaScript 的一个超集,增加了静态类型检查。以下是 TypeScript 中的基础类型:

  1. 布尔值 (Boolean):
    使用 boolean 来定义逻辑值 true 或 false。

    let isDone: boolean = false;
  2. 数字 (Number):
    使用 number 来表示整数和浮点数(包括十六进制、二进制和八进制)。

    let decimal: number = 6;
    let hex: number = 0xf00d;
    let binary: number = 0b1010;
    let octal: number = 0o744;
  3. 字符串 (String):
    使用 string 来表示文本数据。可以使用单引号、双引号或模板字符串。

    let color: string = "blue";
    let fullName: string = `Bob Bobbington`;
  4. 数组 (Array):
    数组可以被声明为元素类型的列表,后面跟上方括号,或者使用泛型的 Array 构造函数。

    let list: number[] = [1, 2, 3];
    let listGeneric: Array<number> = [1, 2, 3];
  5. 元组 (Tuple):
    元组允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

    let x: [string, number];
    x = ["hello", 10]; // OK
  6. 枚举 (Enum):
    枚举类型用于取一组命名的常数值。默认情况下,从 0 开始编号。

    enum Color {Red, Green, Blue}
    let c: Color = Color.Green;
  7. 任意类型 (Any):
    如果你不想在变量上指定类型,TypeScript 编译器就不会检查这个变量的类型。

    let notSure: any = 4;
    notSure = "maybe a string instead";
    notSure = false; // okay, definitely a boolean
  8. 空值 (Null 和 Undefined):
    nullundefined 是所有类型的子类型。也就是说,你可以将 nullundefined 赋值给其他类型的变量。

    let u: undefined = undefined;
    let n: null = null;
  9. 从未类型 (Never):
    表示永远不会出现的值。它通常作为永远不可能到达的代码路径上的返回值类型。

    function error(message: string): never {
        throw new Error(message);
    }
  10. 对象 (Object):
    object 类型表示非原始类型,即任何使用接口定义的类型。

    declare function create(o: object | null): void;
    
    create({ prop: 0 }); // OK

三、变量声明

在 TypeScript 中声明变量可以通过使用 letconst 或者 var 关键字。不过,推荐尽可能使用 letconst,因为它们提供了更好的块级作用域支持,并且避免了 var 可能带来的变量提升(hoisting)问题和函数范围内的意外行为。

  1. 使用 let 声明变量:
    let 允许你声明一个块级作用域的本地变量。与 var 不同的是,let 的变量不会被提升到其包含的作用域顶部。

    let myNumber: number = 42;
  2. 使用 const 声明常量:
    const 用于声明不能重新赋值的变量,即一旦初始化后就不能改变它的引用。注意,如果是一个对象或数组,则虽然不能更改对这个对象或数组的引用,但是可以修改对象的属性或数组的内容。

    const pi: number = 3.14159;
    // pi = 3.14; // Error: Cannot assign to 'pi' because it is a constant.
  3. 使用 var 声明变量 (不推荐):
    var 是 JavaScript 中较老的方式,它定义的变量具有函数作用域(在函数内部)或全局作用域(在函数外部)。它存在变量提升的问题,这可能导致代码难以理解和维护。

    var myVar: string = "Hello";

类型注解

当使用 letconst 声明变量时,你可以选择性地添加类型注解来明确指定变量的类型。TypeScript 通常可以通过类型推断来确定变量的类型,所以类型注解不是必须的,但在某些情况下提供类型注解是很有用的,例如:

  • 当声明一个未初始化的变量时;
  • 当类型不能从初始值中推断出来时;
  • 当你想要为一个变量指定比默认推断更严格的类型时。
let myString: string; // 明确指定类型为 string
myString = "Hello, world!";

推断类型

如果你没有明确指定类型,TypeScript 编译器会根据变量的初始值自动推断出该变量的类型。例如:

let myNumber = 42; // TypeScript 推断 myNumber 的类型为 number

四、函数

魔兽编辑器JASS也有函数,以function开头,endfunction结尾。

TypeScript 的函数是代码组织和复用的基本构建块。它们允许你定义一组执行特定任务的语句,这些语句可以被命名并根据需要多次调用。TypeScript 增强了 JavaScript 函数的功能,提供了静态类型检查和其他特性。以下是 TypeScript 函数的一些关键方面:

1. 函数声明

你可以通过 function 关键字来声明一个函数,并指定参数和返回值的类型。

function add(x: number, y: number): number {
    return x + y;
}

2. 匿名函数

匿名函数是没有名字的函数,通常作为表达式使用,也可以赋值给变量或作为参数传递给其他函数。

let myAdd = function (x: number, y: number): number {
    return x + y;
};

3. 箭头函数

箭头函数提供了一种更简短的语法来编写函数表达式,并且它不会创建自己的 this,而是捕获所在上下文的 this 值。

let myAddArrow = (x: number, y: number): number => x + y;

4. 参数类型

  • 必需参数:默认情况下,所有参数都是必需的。
  • 可选参数:在参数名后加上问号 ? 表示该参数是可选的。
  • 带有默认值的参数:可以在参数定义时为其提供默认值,如果调用时没有提供相应的实参,则使用默认值。
  • 剩余参数(Rest Parameters):允许函数接受任意数量的参数,这些参数会被收集到一个数组中。
function buildName(firstName: string, ...restOfName: string[]): string {
    return firstName + " " + restOfName.join(" ");
}

5. 返回值类型

你可以明确地指定函数返回值的类型。如果函数不返回任何值,则可以使用 void 类型。

function printMessage(message: string): void {
    console.log(message);
}

6. 函数重载

TypeScript 支持函数重载,即同一个函数名可以根据不同的参数列表拥有多个函数签名。这使得你可以为同一函数提供多种调用方式。

function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
    if (d !== undefined && y !== undefined) {
        return new Date(y, mOrTimestamp - 1, d);
    } else {
        return new Date(mOrTimestamp);
    }
}

7. 泛型函数

泛型函数可以处理各种类型的参数,而不必提前确定具体的类型。这增加了函数的灵活性和复用性。

function identity<T>(arg: T): T {
    return arg;
}

8. 回调函数

回调函数是作为参数传递给另一个函数并在某个事件发生时调用的函数。TypeScript 提供了强大的类型系统来确保回调函数的正确性。

function forEach<T>(array: T[], callback: (element: T, index: number) => void): void {
    for (let i = 0; i < array.length; i++) {
        callback(array[i], i);
    }
}

以上就是 TypeScript 中函数的主要特性和概念。使用这些功能,你可以编写更加类型安全、灵活和易于维护的代码。如果你有更多具体的问题或者需要进一步的帮助,请随时提问!


五、类

TypeScript 的类(Class)是面向对象编程的基础,它允许你创建可复用的代码模板,这些模板可以包含属性(成员变量)和方法(成员函数)

1. 定义类

使用 class 关键字来定义一个类,并且可以为类添加构造函数、属性和方法。

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");
console.log(greeter.greet()); // 输出: Hello, world

2. 构造函数

构造函数用于初始化新创建的对象。在 TypeScript 中,你可以直接在构造函数参数上添加修饰符 (public, private, protected) 来自动创建并初始化类的成员。

class Animal {
    constructor(private name: string) {}

    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}


六、对象

在 TypeScript 中,对象是属性和方法的集合,它们可以表示现实世界中的实体或抽象概念。TypeScript 提供了强大的类型系统来定义对象的结构,并确保代码的健壮性和可维护性。

1. 基本对象

最简单的方式是使用对象字面量创建一个对象,这与 JavaScript 类似。你可以直接在大括号 {} 内定义键值对。

let person = {
    name: "Alice",
    age: 30,
    isStudent: false
};

2. 接口(Interfaces)

接口用于定义对象的形状(即对象应该具有哪些属性以及这些属性的类型)。它是一种契约,规定了对象必须满足的结构。

interface Person {
    name: string;
    age: number;
    isStudent?: boolean; // 可选属性
}

let alice: Person = {
    name: "Alice",
    age: 30
};

// 错误:缺少属性 'age'
let bob: Person = { name: "Bob" };

延伸阅读

letconst`的区别

  1. 重新赋值:

    • let: 允许对变量进行重新赋值。
    • const: 定义的变量不能被重新赋值。对于对象或数组类型的 const 变量,虽然你不能改变该变量指向的引用,但可以修改对象自身的属性或数组的内容。
  2. 初始化:

    • let: 不需要在声明时立即初始化,可以在后续代码中赋值。
    • const: 必须在声明时初始化,因为不允许之后再对其赋值。
  3. 作用域:

    • 两者都具有块级作用域,即它们只在 {} 内部有效,这与旧的 var 关键字不同,后者具有函数作用域或全局作用域。
  4. 提升 (Hoisting):

    • letconst 都不会被提升到其包含的作用域顶部,所以在声明之前访问这些变量会导致引用错误(ReferenceError)。而 var 变量会被提升到其作用域顶部,并且会自动初始化为 undefined

letstatic的区别

  1. 上下文:

    • let 是用于声明局部变量的关键字,它适用于任何地方(函数、块、全局)。
    • static 并不是一个用于声明变量的关键字,而是类成员的一种修饰符,用来定义静态方法或属性。静态成员属于类本身而不是类的实例,因此可以通过类名直接访问,不需要创建类的实例。
  2. 生命周期:

    • let 声明的变量存在于它们所声明的作用域内,当执行流离开这个作用域时,变量将不再可用。
    • static 成员是类的一部分,在整个应用程序生命周期中都存在。它们不依赖于特定的实例,因此即使没有创建类的实例,也可以访问静态成员。
  3. 使用场景:

    • 使用 let 来声明那些可能需要改变的局部变量。
    • 使用 static 来定义那些应该属于整个类而不是单个对象的方法或属性,比如工具方法或者共享数据。

为什么在类中经常使用构造函数?

在 TypeScript 中,构造函数(constructor)用于在创建类的实例时初始化对象的状态。构造函数是一个特殊的方法,在使用 new 关键字创建类的新实例时自动调用。以下是为什么在类中经常使用构造函数的一些原因:

  1. 初始化属性:构造函数允许你在创建对象时设置初始值给对象的属性。
  2. 执行必要的设置操作:除了初始化属性外,构造函数还可以用来执行其他设置操作,比如订阅事件、初始化复杂的内部状态等。
  3. 强制依赖注入:通过构造函数传递依赖项是一种常见的设计模式,称为“依赖注入”。这有助于提高代码的可测试性和灵活性。

下面是一个简单的 TypeScript 类示例,展示了如何使用构造函数来初始化属性:

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): void {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const person = new Person("Alice", 30);
person.greet(); // 输出: Hello, my name is Alice and I am 30 years old.

在这个例子中,Person 类有一个构造函数,它接受两个参数 nameage,并在创建 Person 实例时初始化这两个属性。