TypeScript

一 概述

  1. TypeScript是JavaScript的超集,支持ES6+标准
  2. 由微软开发的自由和开源的变成语言
  3. 可编译为纯JS

二 语法

1 基础类型

TypeScript相比JavaScript而言最大的区别就是 类型

1.1 布尔值

1
let isDone : boolean = false;

1.2 数字

和JavaScript一样,TypeScript中的所有数字都是浮点数,使用类型number表示。

1
2
3
4
let dec : number = 6; // 十进制
let hex : number = 0x0C; // 十六进制
let bin : number = 0b01; // 二进制
let oct : number = 0o74; // 八进制

1.3 字符串

使用string表示文本数据类型,可以使用"或者'来表示字符串

1
let name : string = "curry";

模板字符串

模板字符串使用(反引号)包围,并且可以在其中使用 ${ expr }嵌入表达式,expr既可以是模板字符串也可以是可计算的表达式。

1
2
3
let name : string = `zhhc`;
let birth_year : number = 2002;
let sentence : string = `hello, i am ${ name }, and my age is ${ 2022 - birth_year }`;

1.4 数组

两种定义方式

  1. ```typescript // 类型后面加[] let list : number[] = [1, 2, 3];
    1
    2
    3
    4

    2. ```typescript
    // 数组泛型 Array<类型>
    let list : Array<number> = [1, 2, 3];

1.5 元组

元组类型表示 已知数量和类型 的数组,各元素的类型 不必相同

1
let x : [string, number] = ['typescript', 10];

1.6 枚举

枚举是TypeScript中多出来的,和C语言等高级语言类似,可以为一组数组赋予有意义的名字,帮助理解。

1
2
enum Color {Red, Green, Blue}
let c : Color = Color.Red;

可以通过枚举值得到其名字

1
2
enum Color {Red = 1, Green, Blue}
let green_name : string = Color[2]; // "Green"

1.7 任意值

任意值(any)其实就像是JavaScript中的变量一样,可以被赋予任何类型的值,编译器不会进行检查。

对于Object类型的变量,你可以赋任何值,但是不能任意调用方法,即便它真的有这些方法。

1
2
3
4
5
6
7
8
let Any : any = 4;
Any = "Curry";
Any = true;

let anyObject : Object = 4;
anyObject.function(); // Error

let list : any[] = [1, true, "free"];

1.8 空值

空值(void)表示没有任何类型,一般用于函数没有返回值

1
2
3
function func() : void{

}

声明变量没有什么意义,只能赋值nullundefined

1
let void_v : void = null; // or undefined

1.9 Null / Undefined

默认情况下 nullundefined 是所有类型的子类型,即可以将它们赋值给其他类型的变量,但是如果指定了--strictNullChecks,那么只能赋值给各自类型了

1
2
3
4
5
6
7
// without '--strictNullChecks'
let n : number = null;
let s : string = undefined;

// with '--strictNullChecks'
let nll : null = null;
let udfd : undefined = undefined;

1.10 Never

never表示永不存在值的类型。

对于函数,修饰那些 【抛出异常】 或 【根本就不会有返回值】的函数。

1
2
3
4
5
6
7
8
9
function erro(message : string) : never {
throw new Error(message);
}

function deadLoop() : never {
while(true){

}
}

对于变量,never类型是任何类型的子类型,所以可以赋值给任何类型;但是never类型的变量只能由never赋值。

1
2
3
let a : number = never; // OK

let a : never = any; // Error

1.11 类型断言

告诉编译器某个变量的类型

1
2
3
4
5
6
let s : any = "i am a string";

// 1. <断言类型>变量
let len : number = (<string>s).length;
// 2. 变量 as 断言类型
let len : number = (s as string).length;

2 变量声明

TypeScript中支持letconst,JS在ES6就支持了,主要是letvar的区别:主要就是var是函数级作用域,但是let是块级作用域。

3 接口

TypeScript中最核心的原则之一就是 类型,而接口就是为类型检查 定义契约。

像下面这个例子,printConfig函数接受一个符合config接口的参数,而config接口定义了 需要含有值类型为stringname属性

1
2
3
4
5
6
7
8
9
10
interface config{
name : string;
}

function printConfig(Aconfig : config){
console.log(config.name);
}

let cfg = {name : "my config", size : 10};
printConfig(cfg);

可选属性

这里重点需要注意的是:一旦接口中定义了可选的属性,那么被修饰的变量中只能存在要求的属性和可选的属性,不能出现其中不存在的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface config{
name?: string;
size?: number;
}

function printConfig(Aconfig : config){
console.log(config.name);
}

// case one
printConfig({name : "my config", siz : 10}); // Error

// case two
printConfig({name : "my config", siz : 10} as config)

// case three
let c = {name : "my config", siz : 10}
printConfig(c);

函数类型

下面这个接口就定义了一个函数类型,参数是两个number类型的值,返回类型是boolean

1
2
3
4
5
6
7
8
interface func_temp{
(n1: number, n2: number) : boolean;
}

let func1: func_temp;
func1 = function(n1, n2){
return n1 > n2;
}

可索引的类型

1、数字索引=》数组

1
2
3
4
5
interface number_index{
[index : number] : string;
}

let a : number_index = ["a", "b"]; // a[1]

2、字符串索引=》字典

1
2
3
4
5
interface string_index{
[index : string] : string;
}

let b : string_index = {x : "x", y : "y"}; // b['x']

3、 由于当使用number来索引时,JavaScript会将它转换成string然后再去索引对象。所以当两者一起使用时,number索引返回值需要是string索引返回值的子类型。

1
2
3
4
5
6
7
8
9
10
11
12
class A{

}

class B extends A{

}

interface both_index{
[index : number] : B;
[index : string] : A;
}

类类型

这一部分就和其他面向对象高级语言中的类似了。

要注意类静态部分和实例部分的区别

1
2
3
4
5
6
7
8
9
interface ACon{
new ();
}

// Error
// 当一个类实现了一个接口,只会对其实例部分进行检查,而构造函数属于静态部分。
class A implements ACon{
constructor(){}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface ACon{
new () : AInter;
}

interface AInter{
a_func();
}

function createA(acon : ACon) : AInter{
return new acon();
}

class A1 implements AInter{
constructor(){}
}

class A2 implements AInter{
constructor(){}
}

let a1 = createA(A1);
let a2 = createA(A2);

混合类型

一个对象即可以同时作为函数和对象使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Counter {
// as a function
(start: number): string;
// as a object
interval: number;
reset(): void;
}

function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

接口继承类

当使用接口A去继承类B时,它继承类B的所有成员但不包括实现。所以某个类想要实现这个接口A,这个类必须是类B本身或者是类B的子类,否则它不能拥有接口A中的privateprotect成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Control {
private state: any;
}

interface SelectableControl extends Control {
select(): void;
}

class Button extends Control implements SelectableControl {
select() { }
}

// Error: Property 'state' is missing in type 'Image'.
class Image implements SelectableControl {
select() { }
}

4 类

1、类型兼容:TypeScript使用的是结构性类型系统,当比较两种不同的类型时,并不在乎它们从何处而来,如果所有的成员的类型都是兼容的,我们认为它们的类型是兼容的。但是对于带有private或者protected成员的类型时,就需要privateprotect成员来自于同一处声明时,才认为这两个类型是兼容的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
private name : string;
constructor(){}
}

class B extends A {
constructor(){super();}
}

class C{
private name : string;
constuctor(){}
}

let a = new A();
let b = new B();
let c = new C();

a = b; // OK
a = c; // Error

2、参数属性:在构造函数中使用访问限定符修饰参数,使类的属性的声明和赋值合并到一处

1
2
3
class Per{
constructor(private name : string){}
}

3、存取器

TypeScript支持通过getter和setter来截获对对象成员的访问。

1
2
3
4
5
6
7
8
9
10
11
class A {
private _name : string;

get name() : string{
return this._name;
}

set name(newName : string){
//...
}
}

getter和setter是会在访问相关属性时自动调用的,而不需要显式调用。

1
2
3
let a = new A();
a._name = "zhhc"; // 执行 setter 函数
console(a._name); // 执行 getter 函数

4、其他

抽象类、继承、访问修饰符

5、高级技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
class Greet {
static msg = "Hello!";
greeting : string;
greet(){
if(this.greeting){
console.log(this.greeting);
} else {
console.log(Greet.msg);
}
}
}

let GreetMaker : typeof Greet = Greet; // GreetMaker拥有了Greet类的静态部分(构造函数和所有静态变量)

5 函数

1
2
3
4
5
6
7
8
9
// named function
function add(x, y){
return x + y;
}

// anonymous function
let add = function(x, y) {
return x + y;
}

类型

1
2
3
4
5
6
7
8
9
10
11
function add(x: number, y: number) : number{
return x + y;
}

let add = function(x: number, y: number) : number{
retunrn x + y;
}

// 完整类型
let add: (x: number, y: number) => number =
function(x: number, y: number) : number { return x + y; }

可选参数

注意:可变参数放最后

1
2
3
4
5
6
7
function add(x: number, y: number, z?: number) : number{
if(z){
return x + y + z;
} else {
return x + y;
}
}

默认参数

1、默认参数可以放在必须参数的最后面,该参数可以省略

2、默认参数也可以放在必须参数的最前面,该参数不能省略,需要传递undefined获得这个默认值

1
2
3
4
5
6
7
8
9
10
11
12
function add(x: number, y = 10) : number{
return x + y;
}

let a = add(1); // OK

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

let b = add(2); // Error
let c = add(undefined, 2); // OK => 12

剩余参数

剩余参数肯定是需要放在参数列表的最后

1
2
3
4
5
6
7
function add(x: number, y: number, ...nums: number[]) : number{
int sum = x + y;
for(int i = 0; i < nums.length; i++){
sum += nums[i];
}
return sum;
}

this

this是JS中的一个难点。在JavaScript中,this的值在调用时才会确定,这导致了this值会随着调用上下文的改变而改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let per = {
name : "zhhc",
age : 21,
hello : function(){
return function(){
msg = "I am " + this.name + ", " + this.age + "years old!";
return msg;
}
}
}

let Hello = per.hello();
let message = Hello(); // this => window

console.log(message);

解决方法是使用ES6的一个新特性,箭头函数。在箭头函数中,this的值在创建时就确定下来了,箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let per = {
name : "zhhc",
age : 21,
hello : function(){
return () => {
msg = "I am " + this.name + ", " + this.age + "years old!";
return msg;
}
}
}

let Hello = per.hello();
let message = Hello(); // this => per

console.log(message);

this参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Person{
name: string;
age: number;
hello(this: Person): () => string;
}

let per : Person = {
name : "zhhc",
age : 21,
hello : function(this: Person){
return () => {
msg = "I am " + this.name + ", " + this.age + "years old!";
return msg;
}
}
}

let Hello = per.hello();
let message = Hello(); // this => per

console.log(message);

明确地告诉typescriptthis的类型是Person,而不是any

其他

函数重载

6 泛型

泛型总的来说和Java的比较类似。

1、泛型函数

1
2
3
function identity<T>(arg : T) : T {
return arg
}

2、泛型接口

1
2
3
4
5
interface GenericFunc<T>{
(arg: T) : T;
}

let fn : GenericFunc<number> = function(arg : number){ return 2 * arg; }

3、泛型类

1
2
3
class Calc<T>{
add: (x: T, y: T) => T;
}

4、泛型约束

由于泛型参数是针对所有类型,就会导致不能使用一些只有某些类型才具备的属性,例如length,解决方法就是加上泛型约束,对泛型参数加上一定的限制。

1
2
3
4
function getLen<T extends {length: number}>(arg: T) : T{
console.log(arg.length);
return arg;
}

TypeScript
http://example.com/2022/11/12/front-end-technology/TypeScript/
作者
zhc
发布于
2022年11月12日
许可协议