目录:
一、基础知识
1. 声明
2. 类型
3. 运算符
4、语句
二、函数
1、声明函数
2、可选参数
3、rest 参数:
4、返回类型
5、函数作用域&函数调用
6、函数类型
7、箭头函数
8、闭包
9、函数重载
三、类
1、字段
2、方法
3、构造函数
4、可见性修饰符
5、对象字面量
6、this 关键字
四、接口
1、接口属性
2、接口继承
五、泛型类型&泛型函数
1、泛型类和接口
2、泛型约束
3、泛型函数
4、泛型默认值
六、模块
1、导出模块
2、导入模块
(1)静态导入
(2)动态导入
3、顶层语句
参考:ArkTS语言介绍-学习ArkTS语言-基础入门 - 华为HarmonyOS开发者指南
TypeScript 是 JavaScript 的超集,包含了 JavaScript 的所有元素,完全兼容 JavaScript 语言
扩展了 JavaScript 语法(静态类型、类、模块、接口...),其编译结果为 JavaScript 代码
弹出警示框:alert("Hello JavaScript");
输出到控制台:console.log("Hello JavaScript");
输出到页面中:document.writeln("Hello JavaScript");
一、基础知识
1、声明
(1)变量声明:以关键字 let 开头的声明,引入变量,该变量在程序执行期间可以有不同的值。
let hi: string = " hello ";
hi = " hello, world ";
(2)常量声明:以关键字 const 开头的声明,引入只读常量,该常量只能被赋值一次。
const hello: string = " hello ";
(3)自动类型推断:若变量、常量的声明包含了初始值,那就不需要显式指定类型。
let hi1: string = 'hello';
let hi2 = 'hello, world' // 句尾分号可省,但还是建议带着
2、类型
(1)number 类型:任何整数和浮点数,都可以被赋给此类型的变量。
let n1 = 3.14;
let n3 = .5;
let n4 = 1e2;
(2)boolean 类型:此类型由 true 和 false 两个逻辑值组成,常用在条件语句中。
let isDone: boolean = false;
... if (isDone) { console.log ('Done!'); }
(3)string 类型:由单引号或双引号括起来的,零个或多个字符组成的字符序列。
let s1 = 'Hello, world! \n'; // 转义字符
let s2 = 'this is a ' + 'string'; // 拼接
let a = 'Success';
let s3 = `The result is ${a}`; // 字符串模板 ${变量或表达式}
(4)array 类型:array 即数组,是由同类型的数据元素组成的对象。
let names: string[] = ['Alice', 'Bob', 'Carol'];
(5)enum 类型:枚举类型,是预先定义的一组命名值 (枚举常量) 的值类型。
enum ColorSet { Red, Green }
let c: ColorSet = ColorSet.Red;
enum ColorSet { White = 0xFF, Grey = 0x7F } // 常量与值 (整数/字符串) 的绑定
let c: ColorSet = ColorSet.Grey;
(6)union 类型:联合类型,是由多个类型组合成的引用类型,包含了变量可能的所有类型。
class Cat { sleep () {}; meow () {} }
class Dog { sleep () {}; bark () {} } // Cat, Dog, Frog 是一些类型 (类或接口)
class Frog { sleep () {}; leap () {} }
type Animal = Cat | Dog | Frog | number // type 用于定义类型的别名
let animal: Animal = new Cat();
animal = new Frog(); // 联合类型的变量,能赋值为任何组成类型的有效值
animal = 42;
function foo(animal: Animal) {
if (animal instanceof Frog) { animal.leap(); } // animal 在这里是 Frog 类型
animal.sleep(); // Animal 具有 sleep 方法
} // instanceof 用于判断对象的类型
(7)Void 类型、Object 类型、Aliases 类型
void 类型:用于指定函数没有返回值,也可以用于泛型类型参数。
① class Class<T> {
dothing(value:T){ ...
}}
② let instance: Class <void> // 不接受任何参数
Object 类型:任何值,包括基本类型的值 (自动装箱),都能直接被赋给 Object 类型的变量。
Aliases 类型:Aliases 类型为匿名类型提供名称,或为已有类型提供替代名称。
type Matrix = number[][];
type Handler = (s: string, no: number) => string;
type Predicate <T> = (x: T) => boolean; // type 类型别名
type NullableObject = Object | null;
3、运算符
(1)赋值运算:+=、-=、*=、/=、%=、<<=、>>=、>>>=、&=、|=、^=
(2)比较运算:===、!==、==、!=、>、>=、<、<=
(3)算术运算:-、+、--、++、*、/、%
(4)逻辑运算:a&&b、a||b、!a
(5)位运算:a&b、a|b、a^b、~a、a<<b、a>>b、a>>>b
4、语句
(1)If 语句:条件为真时,执行对应的语句,否则执行另一组语句,else 中也可能包含 if 语句。
let s1 = 'Hello';
if (s1) { ... } // 对 boolean 以外的类型,会进行隐式类型转换
else if (condition2) { ... } // 若是双向判断,可用三目运算符替代
else { ... }
(2)Switch 语句:使用 switch 语句,来执行与 switch 表达式值匹配的代码块。
switch (expression) {
case label1:
...; break; // break 可选
case label2 :
case label3: // 如果 label2 或 label3 匹配,则执行
...; break;
default: .... // 默认语句,可省略
}
(3)条件表达式:由第一个表达式的布尔值,来决定返回其它两个表达式中的哪一个。
let isValid = Math.random() > 0.5 ? true : false;
(4)For 语句:for 语句会被重复执行,直到循环退出语句值为 false
for ([init]; [condition]; [update]) { ... }
(5)For-of 语句:使用 for-of 语句可以遍历数组或字符串。
for (z of object) { ... } // z 会依次被赋值为可迭代对象 object 中的各个元素
for (let ch of "a string object") { ... }
(6)While 语句、Do-while 语句:只要 condition 为真值,那么 statements 语句会重复执行。
while (condition) { ... }
do { ... } while (condition)
(7)Break 语句、Continue 语句
break 语句可以终止循环语句或 switch
continue 语句会停止当前循环迭代的执行,并将控制传递给下一个迭代。
(8)Throw 和 Try 语句
throw 语句用于抛出一个异常或错误对象:throw new Error('this error')
try 语句用于捕获和处理异常或错误
function processData(s: string) {
let error: Error | null = null;
try { // 可能发生异常的语句
console.log('Data processed: ' + s);
} catch (e) { // 异常处理
error = e as Error; // as 指定类型
} finally { // finally 语句:异常与否,都得执行
if (error != null) {
console.log(`Error caught: input='${s}', message='${error.message}'`);
}}}
二、函数
1、声明函数
函数声明通过 function 关键字引入一个函数,包含其名称、参数列表、返回类型和函数体。
function add(x: string, y: string): string {
let z: string = `${x} ${y}`;
return z;
}
2、可选参数
可选参数格式为 name?: Type (位于必选参数之后)。另一种可选参数的形式:设置参数默认值
function hello(name?: string) {
if (name == undefined) {
console.log('Hello!');
} else {
console.log(`Hello, ${name}!`);
}}
function multiply(n: number, coeff: number = 2): number { // 可选的另一种形式
return n * coeff;
}
multiply(2); // 返回 2*2
multiply(2, 3); // 返回 2*3 // 默认值被改动
3、rest 参数
又称剩余参数 (位于必选参数之后),该参数允许函数/方法接受任意数量的实参 (数组的形式接收)
function sum(...numbers: number[]): number {
let res = 0; // rest 参数的格式为 ...name
for (let n of numbers)
res += n;
return res;
}
sum() // 返回 0
sum(1, 2, 3) // 返回 6 // 参数个数可以为任意个
4、返回类型
如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型。
function foo(): string { return 'foo'; } // 显式指定返回类型
function goo() { return 'goo'; } // 推断返回类型为 string
function hi1() { console.log('hi'); }
function hi2(): void { console.log('hi'); }
5、函数作用域&函数调用
(1)函数中定义的变量和其他实例,仅可以在函数内部访问,不能从外部访问。
(2)若函数内外的变量同名,则函数内的局部变量定义,将覆盖外部定义。
函数调用:调用函数以执行其函数体,实参值会赋值给函数的形参。
function join(x: string, y: string): string {
let z: string = `${x} ${y}`;
return z;
}
let x = join('hello', 'world');
console.log(x);
6、函数类型
函数类型,常用于定义回调(回调函数是一种特殊函数,它作为参数传递给另一个函数)
type trigFunc = (x: number) => number // 定义函数类型:接收数字参数并返回数字
function do_action(f: trigFunc) {
f(3.141592653589); // 对参数赋值 = 调用函数
}
do_action(Math.sin); // 符合 trigFunc 函数类型,将函数作为参数传入
7、箭头函数
Lambda 函数,最大特点就是写法简洁,语法:(参数列表) => 表达式 或 (参数列表) => { 函数体 }
当函数体只有一个表达式时,可以省略花括号和 return
关键字,直接返回该表达式的结果。
let sum = (x: number, y: number): number => { return x + y; }
let sum1 = (x: number, y: number) => { return x + y; }
let sum2 = (x: number, y: number) => x + y // 自动类型推断
8、闭包
闭包:在函数嵌套的基础上,再加上一个特性:内部函数能访问外部函数的变量,就形成了闭包
1、数据封装和隐藏:闭包能创建私有变量和函数,外界必须通过特定函数接口才能访问和修改。
2、记忆化和缓存:闭包可以将计算结果缓存起来,避免重复计算。
function f(): () => number { // 定义函数 f,不接受参数,且返回一个函数 g
let count = 0;
let g = (): number => { count++; return count; }; // 每次调用,count+1
return g;
}
let z = f(); // z 是一个函数,每次被调用时,count 值都要+1
z(); // 返回:1
z(); // 返回:2
9、函数重载
函数重载,即为同一个函数写入多个同名,但签名不同的函数头,函数实现紧随其后。
function foo(x: number): void; // 第一个函数定义
function foo(x: string): void; // 第二个函数定义
function foo(x: number | string): void { ... }
foo(123); // OK,使用第一个定义
foo('aa'); // OK,使用第二个定义
三、类
类声明:引入一个新类型,并定义其字段、方法和构造函数。
class Person {
name: string = ''
surname: string = ''
constructor (n: string, sn: string) {
this.name = n;
this.surname = sn;
}
fullName(): string {
return this.name + ' ' + this.surname; // 字符串用 "+" 拼接
}}
创建实例:定义类后,使用关键字 new 创建实例,也可以使用对象字面量创建实例。
let p = new Person('John', 'Smith');
console.log(p.fullName()); // new 创建
class Point {
x: number = 0
y: number = 0
}
let p: Point = { x: 42, y: 42 }; // 字面量创建
1、字段
字段,即直接在类中声明的某种类型的变量。类可以具有实例字段或者静态字段。
实例字段:实例字段存在于类的每个实例上。要访问实例字段,需要使用类的实例:
class Person {
name: string = ''
age: number = 0 // 字段在声明时,或者构造函数中尽量显式初始化
constructor(n: string, a: number) {
this.name = n;
this.age = a;
}
getName(): string {
return this.name;
}}
let p1 = new Person('Alice', 25);
p1.name;
let p2 = new Person('Bob', 28);
p2.getName();
静态字段:static 将字段声明为静态。静态字段属于类本身,所有的实例共享一个静态字段。
要访问静态字段,需要使用类名:
class Person {
static numberOfPersons = 0
constructor() {
Person.numberOfPersons++;
}}
Person.numberOfPersons;
getter 和 setter:用于提供对对象属性的受控访问。
class Person {
name: string = ''
private _age: number = 0
get age(): number { return this._age; }
set age(x: number) {
if (x < 0) {
throw Error('Invalid age argument');
}
this._age = x;
}}
let p = new Person();
p.age; // 输出 0
p.age = -42; // 设置无效 age 值会抛出错误
2、方法
方法属于类。类可以定义实例方法或者静态方法。
实例方法:可以访问静态字段,也可以访问实例字段,包括类的私有字段。
class RectangleSize {
private height: number = 0 // private 私有修饰符
private width: number = 0
constructor(height: number, width: number) {
this.height = height;
this.width = width;
}
calculateArea(): number {
return this.height * this.width;
}}
let square = new RectangleSize(10, 10);
square.calculateArea(); // 输出:100
静态方法:static 将方法声明为静态。静态字段属于类本身,所有的实例共享一个静态方法。
class Cl {
static staticMethod(): string {
return 'this is a static method.';
}}
console.log(Cl.staticMethod());
继承:类可以继承另一个类(称为基类),继承基类的字段和方法,但不继承构造函数。
继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法。
class Person {
name: string = ''
private _age = 0
get age(): number {
return this._age;
}}
class Employee extends Person { // extends 表示 “扩展自”
salary: number = 0
calculateTaxes(): number {
return this.salary * 0.42;
}}
含 implements 的类:必须实现列出的接口中,定义的所有方法,但使用默认实现的方法除外。
interface DateInterface {
now(): string;
}
class MyDate implements DateInterface { implements 表示“实现”
now(): string {
... return 'now';
}}
父类访问:关键字 super 可用于访问父类的实例字段、实例方法和构造函数。
class RectangleSize {
protected height: number = 0
protected width: number = 0
constructor (h: number, w: number) {
this.height = h;
this.width = w;
}
draw() { ...
}}
class FilledRectangle extends RectangleSize {
color = ''
constructor (h: number, w: number, c: string) {
super(h, w); // 父类构造函数的调用
this.color = c;
}
draw() {
super.draw(); // 父类方法的调用
... // super.height 可在此处使用
}}
方法重写:子类可以重写其父类中定义的方法的实现。
重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。
class RectangleSize {
area(): number {
...
return 0;
}}
class Square extends RectangleSize {
private side: number = 0
area(): number {
return this.side * this.side;
}}
方法重载签名:为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后。
class C {
foo(x: number): void; // 第一个签名
foo(x: string): void; // 第二个签名
foo(x: number | string): void { // 实现签名
}}
let c = new C();
c.foo(123); // OK,使用第一个签名
c.foo('aa'); // OK,使用第二个签名
3、构造函数
类声明可以包含用于初始化对象状态的构造函数。
如果未定义构造函数,则会自动创建具有空参数列表的默认构造函数。
constructor ([parameters]) { ... }
class Point {
x: number = 0
y: number = 0
}
let p = new Point(); // 自动创建构造函数
派生类的构造函数:构造函数函数体的第一条语句,可以使用 super 来显式调用父类的构造函数。
class RectangleSize {
constructor(width: number, height: number) {
...
}}
class Square extends RectangleSize {
constructor(side: number) {
super(side, side);
}}
构造函数重载签名:为同个构造函数写入多个同名但签名不同的构造函数头,函数实现紧随其后。
class C {
constructor(x: number) // 第一个签名
constructor(x: string) // 第二个签名
constructor(x: number | string) { // 实现签名
}}
let c1 = new C(123); // OK,使用第一个签名
let c2 = new C('abc'); // OK,使用第二个签名
4、可见性修饰符
类的方法和属性都能使用可见性修饰符,其包括:private、protected 和 public(默认)
public 修饰的类成员(字段、方法、构造函数),在程序的任何可访问该类的地方都是可见的。
private 修饰的成员,不能在声明该成员的类之外访问。
protected 修饰符与 private 非常相似,区别是 protected 修饰的成员允许在派生类中访问。
5、对象字面量
对象字面量是一个表达式,可用于创建类实例并提供一些初始值,可以用来代替 new 表达式。
对象字面量的表示方式是:封闭在花括号对 ({}) 中的 "属性名:值" 的列表。
class C {
n: number = 0
s: string = ''
}
function foo(c: C) {}
let c: C
c = {n: 42, s: 'foo'}; // 使用变量的类型
foo({n: 42, s: 'foo'}); // 使用参数的类型
function bar(): C {
return {n: 42, s: 'foo'}; // 使用返回类型
}
let cc: C[] = [{n: 1, s: 'a'}, {n: 2, s: 'b'}]; // 在数组元素类型或类字段类型中使用
Record 类型的对象字面量:泛型 Record<K, V> 用于将键类型的属性,映射到值类型。
类型 K 可以是字符串类型或数值类型,而 V 可以是任何类型。
① let map: Record<string, number> = {
'John': 25,
'Mary': 21, // 常用于初始化该类型的值
}
map['John']; // 25
② interface PersonInfo { // V 可以是任何类型
age: number
salary: number
}
let map: Record<string, PersonInfo> = {
'John': { age: 25, salary: 10},
'Mary': { age: 21, salary: 20}
}
6、this 关键字
关键字 this 只能在类的实例方法中使用。在函数、类的静态方法中,无法使用 this
关键字 this 的指向:调用实例方法的对象、正在构造的对象
class A {
n: number = 0
f1(arg1: this) {} // 编译错误,不支持 this 类型
static f2(arg1: number) {
this.n = arg1; // 编译错误,不支持在类的静态方法中使用 this
}}
function foo(arg1: number) {
this.n = i; // 编译错误,不支持在函数中使用this
}
四、接口
接口声明引入新类型。接口是定义代码协定的常见方式。接口通常包含属性和方法的声明。
任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。
interface Style { color: string } // 属性
interface AreaSize {
calculateAreaSize(): number // 方法的声明
someMethod(): void; // 方法的声明
}
interface AreaSize {
calculateAreaSize(): number // 方法的声明
someMethod(): void; // 方法的声明
}
class RectangleSize implements AreaSize {
private width: number = 0
private height: number = 0
someMethod(): void {
console.log('someMethod called');
}
calculateAreaSize(): number {
this.someMethod(); // 调用另一个方法并返回结果
return this.width * this.height;
}}
1、接口属性
接口属性可以是字段、getter、setter 或 getter 和 setter 组合的形式。
属性字段只是 getter/setter 对的便捷写法。
interface Style { color: string }
interface Style {
get color(): string
set color(x: string)
}
class StyledRectangle implements Style {
color: string = ''
}
class StyledRectangle implements Style {
private _color: string = ''
get color(): string { return this._color; }
set color(x: string) { this._color = x; }
}
2、接口继承
接口可以继承其他接口,其包含被继承接口的所有属性和方法,还可以添加自己的属性和方法。
interface Style { color: string }
interface ExtendedStyle extends Style {
width: number
}
五、泛型类型&泛型函数
泛型允许在定义代码时使用类型参数,而非单一固定为某种类型,使代码更加通用和可重用。
1、泛型类和接口
将类和接口定义为泛型,类的成员变量和方法可以适应不同的类型,接口也可以适用于不同类型。
泛型类的使用、泛型接口的实现,尽量指定类型实参。
class CustomStack<T> { // 声明泛型类,注意 T 只是一个符号
value: T;
setValue(val: T): void { this.value = val; }
getValue(): T { return this.value; }
}
let s = new CustomStack<string>(); // 使用时指定类型型
s.push('hello');
s.push(55); // 编译错误
interface GenericInterface<T> { // 声明泛型接口
doSomething(value: T): void;
}
class ImplementingClass implements GenericInterface<string> { // 使用时指定类型
doSomething(value: string): void {
console.log(value);
}}
2、泛型约束
可以对泛型参数进行约束,参数被限制了,必须符合某些特定的属性和方法。
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void { // 被约束为特定类型
console.log(arg.length);
}
logLength("Hello"); // 正确,因为字符串有 length 属性
logLength(10); // 错误,数字没有 length 属性
3、泛型函数
定义一个函数,使用泛型参数来表示输入参数和返回值的类型,编写更通用的代码。
function last<T>(x: T[]): T { return x[x.length - 1]; } // 多个泛型参数,泛型数组
last<string>(['aa', 'bb']);
last<number>([1, 2, 3]); // 显式设置的类型实参
last([1, 2, 3]); // 隐式设置的类型实参
4、泛型默认值
泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。
class SomeType {}
interface Interface <T1 = SomeType> { }
class Base <T2 = SomeType> { }
class Derived1 extends Base implements Interface { } // Derived1 等价于 Derived2
class Derived2 extends Base<SomeType> implements Interface<SomeType> { }
function foo<T = number>(): T { ... }
foo(); // 此函数等价于下面的调用
foo<number>();
六、模块
程序可划分为多组编译单元或模块。每个模块都有其自己的作用域。
在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。
与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。
1、导出模块
通过 export 导出顶层的声明。未导出的声明名称为私有名称,只能在声明该名称的模块中使用。
export class Point {
x: number = 0
y: number = 0
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}}
export let Origin = new Point(0, 0);
export function Distance(p1: Point, p2: Point): number {
return Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
}
2、导入模块
通过 import 导入声明,用于导入从其它模块导出的实体,并在当前模块中提供其绑定。
导入声明由两部分组成:
1、导入路径。用于指定导入的模块)
2、导入绑定。用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)
(1)静态导入
假设模块具有路径 “./utils” 和导出实体 “X” 和 “Y”
法一:导入绑定* as A 表示绑定名称 “A”,通过 A.name 能访问指定的模块导出的所有实体
import * as Utils from './utils'
Utils.X // 表示来自 Utils 的 X
Utils.Y // 表示来自 Utils 的 Y
法二:导入绑定 { ident1, ..., identN } 表示将导出的实体与指定名称绑定,该名称能用作简单名称
import { X, Y } from './utils'
X // 表示来自 utils 的 X
Y // 表示来自 utils 的 Y
法三:如果标识符列表定义了 ident as alias,则实体 ident 将绑定在名称 alias 下
import { X as Z, Y } from './utils'
Z // 表示来自 Utils 的 X
Y // 表示来自 Utils 的 Y
X // 编译时错误:'X'不可见
(2)动态导入
开发中,有时需要根据条件导入模块或者按需导入模块,此时使用动态导入代替静态导入
import() 语法通常称为动态导入,类似函数的表达式,以这种方式调用,将返回一个promise
该 promise resolve 为一个包含其所有导出的模块对象。该表达式可以在代码中的任意位置调用。
let modulePath = prompt("Which module to load?");
import(modulePath) // 加载模块并返回一个 promise
.then(obj => <module object>)
.catch(err => <loading error, e.g. if no such module>)
在异步函数中,可以使用 let module = await import(modulePath)
async function test() { export function hi() { // say.ts
let ns = await import('./say'); console.log('Hello');
let hi = ns.hi; }
let bye = ns.bye; export function bye() {
hi(); console.log('Bye');
bye(); }
}
3、顶层语句
顶层语句指在模块的最外层直接编写的语句。这些语句不被包裹在任何函数、类、块级作用域中。
顶层语句包括变量声明、函数声明、表达式等。