WM Blog

TypeScript:基础知识

Word count: 3.6kReading time: 14 min
2020/10/03 Share

TypeScript 是什么

TypeScript 2012 年由微软发布, 很多广泛使用的前端框架都在使用TypeScript进行开发。TypeScript 是JavaScript的超集,这意味着它是建立在javaScript语言之上的一门语言,我们可以在TypeScript中使用个各种扩展语言,同时依赖着它对静态类型和面向对象的良好支持,可以让我们写出更加健壮,更可维护,更易阅读的代码。

TypeScript 相比JavaScript有哪些优势

下面这段代码是在JavaScript中求两个数平方和的一个函数

1
2
3
4
5
6
function demo(data) {
return Math.sqrt(data.x **2 + data.y ** 2);
}

demo();
);

在代码编辑器中写这段代码不会提示报错,尽管下面使用这个函数的时候并没有给它传入在定义时需要传入的参数。但是如果使用TS来进行代码编写:

1
2
3
4
5
6
function demo(data:{ x:number, y:number }) {
return Math.sqrt(data.x **2 + data.y ** 2);
}

demo();
);

在函数定义的时候参数的类型可以预先确定,那么如果按照这样写的话编辑器就会提示错误信息,这样可以让我们在开发过程中,就可以定位到潜在问题。
在TS中,当我们在参数中定义了x和y之后在使用这些参数的时候就会产生自动补全提示,这样使得代码的编写更加友好。
另外一点是由于预先定义了参数类型使得代码语义上更加清晰明了,尤其是在一个大型项目中参数非常的时候。

静态类型的理解

先看下面这行代码:

1
const count: number = 100;

相比于JavaScript,count变量不仅仅被限制为了number类型,还同时具备了此类型变量的所有熟悉,这些属性就可以直接通过count.XXX来进行调用。除了一些基础类型,我们可以自定义类型。比如下面代码:

1
2
3
4
5
6
7
8
9
interface Point {
x:number,
y: number
}

const point: Point = {
x: 3,
y: 4
}

在这里我们自定义了一个Point类型,并且指定它有两个域x和y,并且只能是整数,下面我们就是使用一个自定义类型的变量去接收右边的值。
在TS中,基础的静态类型有(number,string,null,undefined,symbol,boolean,void)这几种。
除了基础类型,还有对象类型,除了自己定义的对象类型外,还有一些内置的对象类型,比如数组:

1
const numbers: number[] = [1, 2, 3];

这里声明了一个数字类型的数组,数组里面的元素只能是数字。

有时候我们有一个变量可能为多个类型的需求的时候我们可以通过“ | ”来列举所有需要的类型,比如我们想要一个可以是数字也可以是字符串类型变量:

1
2
let temp: number | string = 123;
temp = '456';

类型注解和类型推断

类型注解就是我们通过显示的告诉TS变量是什么类型,而类型推断是TS自动尝试变量类型,而我们不显示写出。比如:

1
2
3
const first = 1;
const second = 2;
const total = first + second;

在这里这三个变量的类型都可以通过类型推断得出而不需要我们显示说明。那什么情况下需要我们手动加上类型声明呢?看下面的代码:

1
2
3
4
5
function getTotal(first, second) {
return first + second;
}

const total = first + second;

在这里由于传入的参数的类型是不确定的,那么我们就需要在参数里面加上我们需要的类型,如果不加类型那么该变量就会被TS定为any类型。

函数相关的类型

在定义一个函数的时候函数的返回值类型需要说明一下,第一种情况是当函数没有返回值的时候函数的返回值类型应该被声明为void,当一个函数永远不会执行完的时候函数的类型应该被声明为never,下面是两种情况的代码:

1
2
3
4
5
6
7
8
9
//无返回值
function sayHello(): void {
console.log("hello");
}

//不会执行完成
function errorEmitter: never {
throw new Error();
}

对于函数参数解构的类型,我们这样定义它的类型:

1
2
3
4
//注意当只有一个参数解构的时候括号依然需要
function add({first, second}:{first:number, second:number}):number {
return first + second;
}

一个函数如果要声明参数和返回值的类型可以有两种写法:

1
2
3
4
5
6
7
const func =(str: string):number =>{
return parseInt(str, 10);
}

const fun:(str:string) => number = (str) =>{
return parseInt(str, 10);
}

数组和元组

数组

如果我们要定义一个只能存储数字的数组,那么我们只需要这样写:

1
const arr: number[] = [1, 2, 3];

那如果我们数组需要多个类型,比如既可以是数字也可以是字符串:

1
const arr: (number | string)[] = [1, 2, 3];

如果数组元素是对象,比如:

1
2
3
4
const objArr: {name: string, age: number}[] = [{
name: 'mian',
age: 20
}]

在这里数组中的每一项都必须只有name属性,并且对应的值必须是数字。

有些时候变量类型的声明很长而且我们会在多个地方使用的情况下,我们可以为一组变量声明起别名:

1
2
3
4
5
type User =  {name: string, age: number}
const objArr: User [] = [{
name: 'mian',
age: 20
}]

元组

现在假设有这样一个需求,我想在一个数组类型变量里面存储固定长度的元素并且每个元素的类型是固定的,比如我希望定义一个存储三个元素的变量,第一个元素,第二个元素是字符串类型而第三个是数字类型。在这种情况下,就可以使用元组来对变量进行约束:

1
const Info: [string, string, number] = ['wm', 'male', 20];

比如我们需要读取一个Excel或者csv格式的数据的时候,我们就会用到元组来进行约束。

接口(interface)

假设现在我们有两个函数:

1
2
3
4
5
6
const getPerson = (person) => {
console.log(person.name);
}
const setPersonName = (person, name) => {
person.name = name;
}

现在为了让约束person,保证里面一点有一个name属性,其中值一定为string,我们在TS中可以这样:

1
2
3
4
5
6
const getPerson = (person :{name: string}) => {
console.log(person.name);
}
const setPersonName = (person:{name: string}, name) => {
person.name = name;
}

除了可以使用type来给变量的约束起别名之外,还可以使用interface:

1
2
3
4
5
6
interface Person {
name: String
}
const setPersonName = (person:Person, name) => {
person.name = name;
}

那它与type有什么区别呢,type还可以给基础类型取别名,比如:

1
type anotherName = string;

注意:在TS的约定里,能使用interface的地方尽量不使用type。
如果我的interface按照下面这样定义:

1
2
3
4
5
6
7
8
9
interface Person {
name: string;
age: number;
}

const person = {
name: 'mian'
}
getPersonName(person)

在这里由于传入的person没有age,而在定义的时候有,就会报错,而如果我的需求是可以传入这个age也可以不传入呢,也就是说是一个可选的,那么我们就要在变量后面加一个问号来表示这个是可选的:

1
2
3
4
interface Person {
name: string;
age?: number;
}

那如果我person变量里面有我没有在interface中定义的属性会如何呢,比如:

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

const person = {
name: 'mian'
sex: 'male'
}

//第一种情况通过变量缓存,可以通过
getPersonName(person)

//第二种情况直接传入字面量,会报错
getPersonName({
name: 'mian'
sex: 'male'
})

在这里需要分两种情况,当使用变量缓存的时候就不会报错,因为使用的是弱比较,意思就是只有定义里面出现的有就可以,但是如果直接传入的时候使用的就是强比较,需要完全一致。
那如果我们开始的时候不能确定有多少个参数怎么办呢,我们可以使用下面这样的语法:

1
2
3
4
5
6
interface Person {
name: string;
age?: number;
[propName: string: any];
say(): string;
}

这里意思是后面还可以有任意数量只要求有key为string的属性都可以,里面还可以有方法,比如上面的sayHello。

接口可以被类所实现,但是实现的类必须含有接口中所定义的属性和方法。

1
2
3
4
5
6
class User implements Person {
name = 'mian'
say(){
return 'hello'
}
}

接口还可以相互之间进行继承,但是还需要有自己的属性或者方法:

1
2
3
4
interface Teacher extends Person {
//含有Person里所有的属性和方法
teach(): string;
}

在编译成的JavaScript代码里面,接口相关的会被去掉,所以接口只是为了方便我们进行数据类型的校验。

类(class)

类(class)定义与继承

在TS中类的用法和其他面向对象的编程语言非常相似,看下面这段代码:

1
2
3
4
5
6
7
8
9
class Person{
name = 'Jack';
getName(){
return this.name;
}
}

const person = new Person();
console.log(person.getName);

这里输出的应该就是person里面的name属性的值,也就是’Jack’。
类可以被别的类继承,比如有一个Teacher类,需要具备Person类里面的属性和方法,但是作为Teacher还需要有自己的方法,这是就可以继承Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person{
name = 'Jack';
getName(){
return this.name;
}
}

claas Teacher extends Person {
getTeacherName {
return 'wm';
}
}

const teacher = new Teacher();
console.log(teacher.getName);
console.log(teacher.getTeacherName);

这时,就会输出Person以及Teacher里面各种的内容。
此外,父类里面的方法可以被子类重写,比如在Teacher类中可以重写getName()方法。如果子类重写了父类的方法还希望用到父类的方法就需要用到关键字super。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person{
name = 'Jack';
getName(){
return this.name;
}
}

claas Teacher extends Person {
getName {
return super.getName()+'wm';
}
}

const teacher = new Teacher();
console.log(teacher.getrName);
//输出的是'Jackwm'

类(class)属性的访问类型

类其中的属性,方法有不同的访问级别,当不显示的声明时,默认的级别是public,意思是可以被类中与类外访问,以下可以看出,在类定义的外部去使用name属性不会出错,而另一种情况是将name属性声明为private,这时就不能在类的外部使用name,但是方法sayHello可以调用name属性,因为其在类的内部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

//此时class类型为public
class Person{
name: string;
public sayHello(){
this.name;
console.log('hello');
}
}

const person = new Person();
person.name = 'wm';
//在类外调用不会出错
console.lgo(person.name)
person.sayHello();

另外一种访问级别是protected, 被次关键字修饰的方法和属性只能在类内和其子类中被使用,下面这种情况就是合法的,因为Teacher类是继承自Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

//此时class类型为public
class Person{
protected name: string;
public sayHello(){
this.name;
console.log('hello');
}
}

class Teacher {
public syaBye(){
this.name
}
}

类(class)的构造器

一个类的构造器函数会在这个类被new出来的时候执行。看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11

//此时class类型为public
class Person{
name: string;
constructor(name: string) {
this.name = name
}
}

const person = new Person('wm');
console.log(person.name);

constructor 函数就是构造器函数,将接收到的参数name赋值给类中的name属性,这里和java非常类似。在TS中,还有一种更为简洁的写法:

1
2
3
4
5
6
7
8
9

//此时class类型为public
class Person{
//构造器中的参数需要加上public修饰符
constructor(public name: string) {}
}

const person = new Person('wm');
console.log(person.name);

当子类继承了父类之后,如果希望添加自己新的属性的话,需要在子类的构造器中使用super关键字调用父类的构造函数,需要注意的是,需要按照父类的构造器要求传入相应的参数。下面是一个例子:

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

//此时class类型为public
class Person{
constructor(public name: string) {}
}

class Teacher extends Person {
constructor(public age: number){
//需要按照父类的方式传入参数
super('wm')
}
}

注意: 当父类中没有显示的写构造函数的时候子类也要调用一个参数为空的super函数。

类(class)的Getter与Setter

有时候我们希望不让外部直接访问和修改对象的属性,而通过类的方法,那这时就需要用到TS类中提供的get和set方法。和Java不同的是在外部函数的调用需要像属性一样的访问形式,不加括号,具体看这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
constructor(private: _name:string){}
get name(){
return this._name+' ';
}
set name(name:string){
this._name = name;
}
}

const person = new Person('wm');
console.log(person.name);
person.name = 'wm';

在这个例子中,get和set方法相当于给类中的一个属性,使用属性的方式进行访问,这样可以保护对象内部的私有属性_name不被直接暴露给外部。

单例模式

有时候我们想要一个类只有一个实例而不是每次调用构造函数都会返回一个新的实例,这个时候该怎么办呢,这里需要用到一个新的关键字static,用它修饰的属性和方法会被挂载到类上而不是对象。想要一个类只有一个实例,就需要在这个类中缓存生成出来的那个实例,并且类的构造函数应该只能在类中自定义的函数使用,而自定义的函数来控制是否调用构造器函数,而这个函数也需要是static的,这样可以通过类似属性的访问方式获取。具体的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Demo {
private static instance Demo;
private constructor(){};

static getInstance(){
if(!this.instance){
this.instance = new Demo();
}
return this.instance;
}
}

//外部只能通过const demo = Demo.getInstance();来获取。

抽象类

有时候有狠多类里面有一些通用的属性或者方法,但是他们的实现不完全相同,比如三角形,圆,长方形等多边形都具有面积这个属性,但是计算面具的方法却不相同,这时我们就可以通过定义一个抽象类Gemo,在其中定义一个抽象方法getArea()。看下面例子:

1
2
3
4
5
6
7
8
9
10
11
abstract class Gemo {
//抽象方法只能被定义不可以实现
abstract getArea():name;
}

class Circle extends Gemo {
//子类如果继承抽象类,则必须实现其中的抽象方法。
getArea(){
return 'CircleArea';
}
}

注意:抽象类也可以有具体的方法和属性。

CATALOG
  1. 1. TypeScript 是什么
  2. 2. TypeScript 相比JavaScript有哪些优势
  3. 3. 静态类型的理解
  4. 4. 类型注解和类型推断
  5. 5. 函数相关的类型
  6. 6. 数组和元组
    1. 6.1. 数组
    2. 6.2. 元组
  7. 7. 接口(interface)
  8. 8. 类(class)
    1. 8.1. 类(class)定义与继承
    2. 8.2. 类(class)属性的访问类型
    3. 8.3. 类(class)的构造器
    4. 8.4. 类(class)的Getter与Setter
    5. 8.5. 单例模式
    6. 8.6. 抽象类