image-20240227102300858

[TOC]

0. 背景

根据鸿蒙官方的说明:

ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript(简称TS)生态基础上做了进一步扩展,继承了TS的所有特性,是TS的超集。因此,在学习ArkTS语言之前,建议开发者具备TS语言开发能力。

所以,首先需要学习 TypeScript。

TypeScript 是一种由微软开发的开源编程语言,它是 JavaScript 的一个超集,添加了静态类型和其他一些新特性。TypeScript 可以编译为普通的 JavaScript 代码,因此可以在任何支持 JavaScript 的地方运行,并且可以与现有的 JavaScript 代码无缝集成。

以下是 TypeScript 的一些主要特点和优势:

  1. 静态类型:TypeScript 引入了静态类型系统,允许开发人员为变量、函数参数、函数返回值等添加类型注解。这有助于在编码阶段捕获类型错误,提高代码的可读性、可维护性和可靠性。
  2. 类型推断:TypeScript 可以根据上下文自动推断变量的类型,这意味着您不必为每个变量都显式指定类型,可以节省大量的代码。
  3. 面向对象编程:TypeScript 支持类、接口、继承、多态等面向对象编程的特性,使得代码更具组织性和可扩展性。
  4. 工具支持:TypeScript 提供了丰富的工具支持,包括代码编辑器(如 Visual Studio Code)、调试器、构建工具等,可以提高开发效率。
  5. ES6+ 支持:TypeScript 支持最新的 ECMAScript 标准,包括箭头函数、解构赋值、类、模块等特性,可以让您在开发过程中使用最新的 JavaScript 功能。

总的来说,TypeScript 是一种强类型的、可扩展的、面向对象的编程语言,它为 JavaScript 开发者提供了更好的工具和语言特性,编写更加可靠和易维护的代码。

1. 基础类型

和其他语言一样, TypeScript 也有自己的基础类型:

  • 布尔值 : boolean
  • 数字 : number, 包含整数, 浮点数, 进制数字等
  • 字符串 : string
  • 动态类型 : any(动态内容)
  • 空 : null 和 undefined(所有类型的子类型)

示例如下:

let isComplete : boolean = false;

let a :number = 3; //整数
let b :number = 3.0; //浮点数
let doubleNumber: number = 3.141592653589793; //高精度浮点数
let c :number = 0xf00; //十六进制
let d :number = 0b001; //二进制
let f :number = 0o744; //八进制

let s: string = "123";
let s1: string = `123`;
let s3: string = `name is ${s1},
    age is 12`; //字符模板可换行

let notSure: any = 4;
notSure = "123";
notSure = false;

let u:null = null;
let n:undefined = undefined;

除了上述类型外还有:

  • 数组
  • 元组 Tuple : 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 stringnumber类型的元组。
  • Void : void类型像是与any类型相反,它表示没有任何类型. 作为函数的返回值
  • Never : 表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。

举例如下:

//数组
let list:number[] = [1,2,3];
let list:Array<number> = [1,2,3];
console.log(list[0]);

// 元组
let x:[string, number];
x = ["hello", 0];
console.log(x[0] + "-" + x[1]);
//当访问一个越界的元素,会使用联合类型替代:
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型
console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString
x[6] = true; // Error, 布尔不是(string | number)类型

//Void
function warnUser(): void {
    console.log("This is my warning message");
}

// Never
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

类型之间可以进行推断, 类似于 Java/C# 类型转换:

let someValue: any = "this is a string";
// 强制
let strLength: number = (<string>someValue).length;
// as 
let strLength: number = (someValue as string).length;

2. 程序声明

TypeScript语言的程序结构包含:

  • 变量声明 : var a = 10;
  • 函数声明 : funtion name(){}

2.1变量声明

变量声明可以使用 var 和 let, 两种声明有一些重要的区别:

  1. 作用域

    • var 声明的作用域是函数作用域或全局作用域。在函数内部声明的 var 变量在函数外部仍然是可访问的,这种行为被称为变量提升(hoisting)。
    • let 声明的作用域是块级作用域(例如 {} 内部)。在块级作用域外部无法访问块级作用域内的 let 变量,而且不存在变量提升。
  2. 重复声明

    • 使用 var 可以重复声明同一个变量,不会报错,而且后面的声明会覆盖前面的声明。
    • 使用 let 声明同一个变量如果在同一个作用域内重复声明,会报错 SyntaxError。
  3. 变量提升

    • 使用 var 声明的变量会被提升到其所在作用域的顶部,即使在变量声明之前使用变量也不会报错,但其值为 undefined
    • 使用 let 声明的变量不会被提升,如果在变量声明之前使用变量会报错 ReferenceError。
  4. 全局对象属性

    • 使用 var 声明的变量会成为全局对象的属性,例如 window 对象(浏览器环境)。
    • 使用 let 声明的变量不会成为全局对象的属性。

总的来说,推荐使用 let 来声明变量,因为它更安全、更符合块级作用域的使用方式,而且可以避免一些意外的行为。

2.2 函数声明

函数按照名称, 参数和返回值分为:

  • 有名函数和匿名函数
  • 无参函数和有参函数
  • 无返回值和有返回值

有名函数与无名函数

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

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

可选参数和默认参数

//固定参数
function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}
let result1 = buildName("Bob");                  // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result3 = buildName("Bob", "Adams");         // ah, just right


//可选参数
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
let result1 = buildName("Bob");  // works correctly now
let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result3 = buildName("Bob", "Adams");  // ah, just right


//默认参数
function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}
let result1 = buildName("Bob");                  // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined);       // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result4 = buildName("Bob", "Adams");         // ah, just right

可变参数

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

2.3 控制语句

TypeScript 中常用的控制语句与 JavaScript 中的类似,主要包括以下几种:

  1. 条件语句(Conditional Statements)

    • if 语句:根据条件执行不同的代码块。
    • else if 语句:在 if 条件不满足时执行新的条件判断。
    • else 语句:在所有条件都不满足时执行的代码块。
    let num: number = 10;
    
    if (num > 0) {
        console.log("Positive");
    } else if (num < 0) {
        console.log("Negative");
    } else {
        console.log("Zero");
    }
  2. 循环语句(Loop Statements)

    • for 循环:重复执行指定次数的代码块。
    • while 循环:在指定条件为真时重复执行代码块。
    • do...while 循环:先执行一次代码块,然后在指定条件为真时重复执行代码块。
    for (let i = 0; i < 5; i++) {
        console.log(i);
    }
    
    let j = 0;
    while (j < 5) {
        console.log(j);
        j++;
    }
    
    let k = 0;
    do {
        console.log(k);
        k++;
    } while (k < 5);
  3. switch 语句:根据表达式的值选择执行不同的代码块。

    let fruit: string = "apple";
    
    switch (fruit) {
        case "apple":
            console.log("Apple");
            break;
        case "banana":
            console.log("Banana");
            break;
        default:
            console.log("Unknown fruit");
    }
  4. breakcontinue 语句:在循环中控制循环的执行流程。

    • break 语句用于立即退出循环。
    • continue 语句用于跳过当前循环的剩余代码,直接进入下一次循环的迭代。
  5. for...of 语句

    • 用于遍历可迭代对象(如数组、集合等)的元素。
    • 提供了一种简洁的方式来迭代对象的元素。
    let colors: string[] = ["red", "green", "blue"];
    
    for (let color of colors) {
        console.log(color);
    }
  6. for...in 语句

    • 用于遍历对象的可枚举属性。
    • 迭代对象的所有可枚举属性名称,包括原型链上的属性。
    let obj = { a: 1, b: 2, c: 3 };
    
    for (let key in obj) {
        console.log(key); // 输出 "a", "b", "c"
        console.log(obj[key]); // 输出 1, 2, 3
    }

    for..offor..in均可迭代一个列表;但是用于迭代的值却不同:

  7. for..in迭代的是对象的 的列表
  8. for..of则迭代对象的键对应的值。

    3. 面相对象

    和别的面相对象语言一样, TypeScript:

  9. 类、接口、泛型
  10. 使用 extends 继承类
  11. 使用 implement 实现接口

    3.1 修饰符

    TypeScript也有相应的修饰符:

  12. public

    • 默认的修饰符,如果没有显式地指定修饰符,默认为 public。
    • 可以被类的实例访问,也可以被子类继承。
  13. private

    • 私有成员,只能在定义它们的类内部访问。
    • 不能在类的外部或子类中访问。
  14. protected

    • 受保护的成员,可以在定义它们的类内部和该类的子类中访问。
    • 不能在类的外部访问。
  15. readonly

    • 只读成员,表示该属性只能在声明时或构造函数中被赋值,之后无法修改。
    • 类似于常量,但是只对属性起作用。

    3.2 Object

    在 TypeScript 中,Object 是 JavaScript 中的原生对象类型,表示一个普通的 JavaScript 对象。在 TypeScript 中,Object 类型可以用来表示任意的 JavaScript 对象,它是所有对象的顶级类型。在 TypeScript 中,Object 类型可以作为任何类型的超类型,因此可以将任何值分配给 Object 类型的变量。

    创建对象

    let obj: Object = {};

    这里我们声明了一个类型为 Object 的变量 obj,并初始化为一个空对象。这样做的好处是,obj 可以引用任何类型的对象。

    对象方法

    Object 类型具有许多内置的方法,可以对对象进行操作。例如:

  16. Object.keys(obj):返回一个数组,包含对象的所有可枚举属性的名称。
  17. Object.values(obj):返回一个数组,包含对象的所有可枚举属性的值。
  18. Object.entries(obj):返回一个数组,包含对象的所有可枚举属性的键值对。
  19. Object.assign(target, ...sources):将一个或多个源对象的所有可枚举属性复制到目标对象,并返回目标对象。

    let obj = { a: 1, b: 2, c: 3 };

console.log(Object.keys(obj)); // 输出 ["a", "b", "c"]
console.log(Object.values(obj)); // 输出 [1, 2, 3]
console.log(Object.entries(obj)); // 输出 [["a", 1], ["b", 2], ["c", 3]]


#### 扩展对象

可以使用对象展开运算符 `...` 来扩展对象。

let obj1 = { a: 1, b: 2 };
let obj2 = { c: 3, d: 4 };

let mergedObj = { ...obj1, ...obj2 };

console.log(mergedObj); // 输出 { a: 1, b: 2, c: 3, d: 4 }


在 TypeScript 中,`Object` 类型通常用于表示任意的 JavaScript 对象,并且提供了一些内置的方法来操作对象。但需要注意的是,`Object` 类型并不包括 JavaScript 中一些特殊的对象类型,比如数组、函数、正则表达式等。

### 3.3 类,接口定义与实现

具体以下面代码为例:

interface PersonInfo {

name: string;
age: number;
gender: string;
id: number;
introduce(): void; // 增加一个行为函数定义

}

class Person implements PersonInfo {

public name: string;
private age: number;
protected gender: string;
readonly id: number;

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

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

}

class Employee extends Person {

constructor(name: string, age: number, gender: string, id: number) {
    super(name, age, gender, id);
}

showInformation() {
    console.log(`Name: ${this.name}, Gender: ${this.gender}`);
}

}

const personInfo: PersonInfo = {

name: "Alice",
age: 30,
gender: "female",
id: 12345,
introduce() {
    console.log(`Hello, my name is ${this.name}. I am ${this.age} years old.`);
}

};

const person: Person = new Person(personInfo.name, personInfo.age, personInfo.gender, personInfo.id);
console.log(person.name); // 可以访问,输出 "Alice"
console.log(person.age); // 报错,age 是私有属性,无法在类的外部访问
console.log(person.gender); // 报错,gender 是受保护的属性,无法在类的外部访问
console.log(person.id); // 可以访问,输出 12345
person.introduce(); // 调用行为函数

const employee: Employee = new Employee("Bob", 35, "male", 67890);
console.log(employee.name); // 可以访问,输出 "Bob"
console.log(employee.gender); // 报错,gender 是受保护的属性,无法在类的外部访问
employee.showInformation(); // 可以访问,输出 "Name: Bob, Gender: male"


### 3.4 包装类

在 JavaScript 中,原始数据类型(Primitive Types)和对象类型(Object Types)之间存在一种关联,这种关联通常被称为包装对象(Wrapper Objects)。

JavaScript 为每种原始数据类型(除了 `null` 和 `undefined`)都提供了对应的包装对象,这些包装对象是由 JavaScript 引擎自动创建的,并提供了一些额外的功能。这些包装对象是临时创建的,用于在必要时使原始类型的值能够像对象一样进行操作。

下面是 JavaScript 中主要的包装对象:

- `Number` 对象用于处理数字类型的值。
- `String` 对象用于处理字符串类型的值。
- `Boolean` 对象用于处理布尔类型的值。
- `Symbol` 对象用于处理符号类型的值。

例如,对于字符串类型的值,JavaScript 提供了 `String` 对象,可以使用 `String` 对象的方法来操作字符串:

let str = "Hello";
console.log(str.length); // 输出 5,字符串的长度

let strObj = new String("Hello");
console.log(strObj.length); // 输出 5,字符串对象的长度


在这个例子中,`str` 是一个原始的字符串类型的值,而 `strObj` 是一个 `String` 对象,它们都具有 `length` 属性,用于返回字符串的长度。虽然 `str` 不是对象,但 JavaScript 在必要时会将其自动转换为一个临时的 `String` 对象,以便调用 `length` 属性。

在 TypeScript 中,也存在类似的包装对象的概念,但通常开发者不需要显式地使用包装对象,因为 TypeScript 会隐式地处理原始类型和对象类型之间的转换。

## 4. 泛型

TypeScript 中的泛型(Generics)是一种编程模式,用于在编写代码时增强灵活性和可重用性。泛型使得在定义函数、类、接口等数据类型时可以使用参数化类型,从而使这些数据类型具有更广泛的适用性。泛型能够让我们编写出更加通用、灵活的代码,同时提高代码的类型安全性。

### 4.1 泛型函数

function identity(arg: T): T {

return arg;

}

let output = identity("hello"); // 输出 "hello"
let output2 = identity(123); // 输出 123


在这个例子中,`identity` 函数是一个泛型函数。它接受一个参数 `arg`,并返回相同类型的参数。通过在函数名后面使用尖括号 `<T>` 来定义泛型,并在参数类型和返回类型中使用泛型类型 `T`。

### 4.2 泛型类

class GenericClass {

private value: T;

constructor(value: T) {
    this.value = value;
}

getValue(): T {
    return this.value;
}

}

let stringObject = new GenericClass("hello");
console.log(stringObject.getValue()); // 输出 "hello"

let numberObject = new GenericClass(123);
console.log(numberObject.getValue()); // 输出 123


在这个例子中,`GenericClass` 是一个泛型类,它接受一个类型参数 `T`,并将该类型参数用于成员变量 `value` 的类型和方法 `getValue` 的返回类型。

### 4.3 泛型接口

interface GenericInterface {

value: T;
getValue(): T;

}

class MyClass implements GenericInterface {

constructor(public value: T) {}

getValue(): T {
    return this.value;
}

}

let stringObject: GenericInterface = new MyClass("hello");
console.log(stringObject.getValue()); // 输出 "hello"

let numberObject: GenericInterface = new MyClass(123);
console.log(numberObject.getValue()); // 输出 123


在这个例子中,`GenericInterface` 是一个泛型接口,它定义了一个属性 `value` 和一个方法 `getValue`,并都使用了泛型类型 `T`。`MyClass` 类实现了 `GenericInterface` 接口,并通过类型参数 `T` 指定了 `value` 的类型。

## 5. 枚举

在 TypeScript 中,枚举(enums)是一种数据类型,用于定义命名的常量集合。枚举允许我们为一组相关的常量赋予友好的名称,并且在代码中以一种更具可读性和可维护性的方式来引用它们。

### 5.1 数字枚举

enum Direction {

Up,
Down,
Left,
Right,

}

let playerDirection: Direction = Direction.Right;
console.log(playerDirection); // 输出 3,即 Right 对应的数字值


在这个例子中,`Direction` 枚举定义了一组方向常量,它们分别对应了数字值 0、1、2 和 3。默认情况下,枚举成员的值从 0 开始递增。

### 5.2 字符串枚举

enum Color {

Red = "RED",
Green = "GREEN",
Blue = "BLUE",

}

let primaryColor: Color = Color.Red;
console.log(primaryColor); // 输出 "RED"


在这个例子中,`Color` 枚举定义了一组颜色常量,它们分别对应了字符串值 "RED"、"GREEN" 和 "BLUE"。字符串枚举的每个成员必须有初始化器,并且不能自动递增。

### 5.3 值与名称的双向映射

enum KeyCode {

Up = 38,
Down = 40,
Left = 37,
Right = 39,

}

console.log(KeyCode.Up); // 输出 38
console.log(KeyCode[38]); // 输出 "Up"


在这个例子中,`KeyCode` 枚举定义了一组键码常量,它们分别对应了相应的键码值。通过枚举成员的名称来获取其对应的值,或者通过枚举成员的值来获取其对应的名称。

### 5.4 常量和计算成员

enum Weekday {

Monday = 1,
Tuesday = Monday + 1,
Wednesday = Tuesday + 1,
Thursday = Wednesday + 1,
Friday = Thursday + 1,
Saturday = 7,
Sunday = Saturday,

}

console.log(Weekday.Monday); // 输出 1
console.log(Weekday.Tuesday); // 输出 2
console.log(Weekday.Sunday); // 输出 7


在这个例子中,`Weekday` 枚举定义了一组表示星期的常量,其中一些成员是通过计算得出的。

## 6. 高级类型

TypeScript 提供了许多高级类型来增强代码的灵活性和表达能力。下面是一些常用的高级类型:

### 6.1 交叉类型(Intersection Types)

交叉类型允许我们将多个类型合并为一个新的类型。新类型将具有所有类型的成员。

interface A {

propA: number;

}

interface B {

propB: string;

}

type AB = A & B;

let obj: AB = {

propA: 123,
propB: "hello"

};


在这个例子中,`AB` 类型是 `A` 和 `B` 接口的交叉类型,`obj` 变量既有 `propA` 也有 `propB` 属性。

### 6.2 联合类型(Union Types)

联合类型允许一个值可以是几种类型之一。使用 `|` 符号来表示。

let var: string | number;
var = "hello";
var = 123;


在这个例子中,`var` 变量可以是字符串类型或数字类型。

### 6.3 类型别名(Type Aliases)

类型别名可以为任意类型创建一个新名字。

type Age = number;
type Person = {

name: string;
age: Age;

};

let person: Person = {

name: "Alice",
age: 30

};


在这个例子中,`Age` 是 `number` 类型的别名,`Person` 是一个包含 `name` 和 `age` 属性的对象类型。

### 6.4 索引类型(Index Types)

索引类型允许通过索引访问对象的属性。

interface Person {

name: string;
age: number;

}

type PersonProp = keyof Person;

let key: PersonProp = "name"; // "name" | "age"


在这个例子中,`PersonProp` 是 `Person` 接口的属性名称的联合类型,`key` 变量可以是 `"name"` 或 `"age"`。

### 6.5 映射类型(Mapped Types)

映射类型允许我们基于已有类型创建新类型。

interface Person {

name: string;
age: number;

}

type ReadonlyPerson = {

readonly [K in keyof Person]: Person[K];

};

let person: ReadonlyPerson = {

name: "Alice",
age: 30

};

person.name = "Bob"; // 报错,readonly 属性不能被修改


在这个例子中,`ReadonlyPerson` 类型将 `Person` 接口的所有属性都设为只读。

这些高级类型可以帮助我们更好地组织和表达代码,并且提供了更多的类型安全性和灵活性。



## 7. 总结

以上就是大概的 TypeScript 的基础知识,学习完毕以后再学习 ArkTS 会更简单。 接下来学习 ArkTS。

关注我,持续学习 鸿蒙App开发~