01.TypeScript 基础语法入门

本文介绍了TypeScript的基础语法和环境搭建,包括安装、类型系统、静态检查、代码提示、类与对象、函数类型、数组与元组、接口、类以及静态与抽象方法。TypeScript作为JavaScript的超集,提供静态类型检查和代码提示,提高了代码的可维护性和健壮性。学习TypeScript需要了解其安装、基础类型、接口、类的使用以及如何将TS代码编译为JS代码。
摘要由CSDN通过智能技术生成

TypeScript 基础语法入门

1. 安装

使用 npm 安装即可

npm install -g typescript

2. TypeScript 介绍

TypeScript 是 JavaScript 的超集,因此 TypeScript 可以使用 JavaScript 的所有语法的同时,拓展新的语法。这些新语法使得 TS 相比于 JS 而言,可维护性和健壮性更强。TypeScript 需要编译成 JavaScript 代码后才能运行。

TS 的优势

静态语言:变量类型声明后,该变量的类型不可改变。TS 就是静态语言。大部分老牌语言,如 C++,Java 等就是静态语言。

而 JS 相反,是动态语言,类型可以随便改变,虽然灵活但是带来了许多风险和麻烦。下面进行阐述 TS 的优点:

  1. TS 配合编辑器提供了强大的代码检查能力

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

    demo 里指明了需要一个参数,这个参数里需要 x 参数和 y 参数,类型都是 number。如果 demo 传的参数不符合这个条件,就会报错。

    静态类型使得在开发过程中就可以发现潜在的问题,而不是代码执行的时候才发现。

  2. TS 配合编辑器提供了代码提示能力

    如上代码,因为有了静态检查,data 里肯定会有 x 和 y,因此编辑器就会给与相应的自动提示,写代码时更加友好。

  3. 代码语义更清晰易懂。看 JS 代码必须要深入方法逻辑才知道传进去的参数是什么内容,而 TS 不需要。

    上面的代码稍微优化一下:

    interface Point {
      x: number;
      y: number;
    }
    
    function demo(data: Point) {
      return Math.sqrt(data.x ** 2 + data.y ** 2);
    }
    

3. TypeScript 基础环境搭建

3.1 所需环境

  1. 有 node 环境

    必须要有 node,学习 TypeScript 肯定都安装 node 了,没啥好说的

  2. 全局安装 TypeScript

    npm install typescript -g
    
  3. vscode 里安装 prettier 插件(推荐)

    代码一键格式化利器!

3.2 如何运行 TS

node 只支持 JS,不支持 TS 呀,那该怎么办呢?

这里有两个方法:

tsc 命令编译 TS 为 JS 后,用 node 运行

在终端命令下执行 tsc 命令

tsc demo.ts

执行完后会发现在当前文件夹下产生了同名的 js 文件,这就是编译后的 js 文件。

然后用 node 命令执行即可成功运行

node demo.js
使用 ts-node 直接运行

全局安装 ts-node

npm install -g ts-node

安装后直接 ts-node 命令执行即可运行

ts-node demo.ts

4. 基础类型和对象类型

上面声明了其中一种类型:number,还有很多种类型。

类型主要分成两种类型:

  1. 基础类型

    null, undefined, number, string, symbol, boolean, void

  2. 对象类型

    数组、对象、类、function 等

    // 类
    class Person {}
    const sjh: Person = new Person();
    
    // 对象
    const teacher: { name: string; age: number } = {
      name: "sjh",
      age: 18,
    };
    
    // 数组
    const numbers: number[] = [1, 2, 3];
    
    // 函数,接收一个 string 类型参数,返回一个数字类型(有两种写法,第二种方式更好理解)
    const func = (str: string): number => {
      return parseInt(str);
    };
    const func2: (str: string) => number = (str) => {
      return parseInt(str);
    };
    

5. 类型注解和类型推断

type annotation 类型注解:告诉 TS 变量是什么类型。

let count: number;
count = 123;

type inference 类型推断:TS 自动推断该变量的类型

let countInference = 123;

总结:写 TS 很重要的一个原因是,希望每个变量都有固定的类型。如果 TS 能够自动分析变量类型,那么我们可以什么都不需要做了。如果 TS 无法分析变量类型的话,我们就需要使用类型注解。推荐该写类型注解就写类型注解,提高可读性。

6. 函数相关类型

需要给函数里的参数和返回值都写上类型注解。

如上,需要两个 number 类型的参数,返回一个 number 类型的结果。

function add(first: number, second: number): number {
    return first + second;
}

如果没有返回值,类型为 void,代表返回值为空

function sayHello(): void {
    console.log("hello");
}

never 表示这个函数永远执行不到末尾:

function errorEmitter(): never {
    // 例如,while(true) 后边的代码永远执行不到
    // 或者中间抛出异常,后边的代码也无法执行
}

如果函数接收到解构内容,下面这种写法才是正确的:

把整个结构的内容当成一个变量。

function add({ first, second }: { first: number; second: number }): number {
  return first + second;
}

const total = add({ first: 1, second: 2 });

7. 数组和元组

7.1 数组

数组的类型注解如下:

// 里边存储的是数字类型
const numberArr: number[] = [1, 2, 3];
// 里边存储的是数字或字符串类型
const arr: (number | string)[] = [1, 2, "3"];

如果数组里边要放对象,需要这样写:

const objectArr: { name: string; age: number }[] = [
  {
    name: "sjh",
    age: 12,
  },
];

但是这样写未免有点臃肿,因此引入类型别名(type alias):

type User = {
  name: string;
  age: number;
};

const objectArr: User[] = [
  {
    name: "sjh",
    age: 12,
  },
];

后边可以发现,用 class 也是符合要求的,不用 new 出来的实例也不会报错:

class Teacher {
  name: string;
  age: number;
}

const objectArr: Teacher[] = [
  new Teacher(),
  {
    name: "sjh",
    age: 12,
  },
];

7.2 元组

适用于数组的个数确定、每个位置的元素的位置的属性都确定的情况。如下面示例:

const teacherInfo: [string, string, number] = ["sjh", "male", 18]

元组就非常适用于读取 csv 的情况:

const userList: [string, string, number][] = [
  ["user1", "male", 19],
  ["user2", "female", 18],
];

8. Interface 接口

inferface,用来定义通用又比较复杂的类型,在其他地方就可以复用了,提高可读性。

接口是一个语法校验的工具,经过 tsc 编译后并不会转化成 JS 代码。

会有人会想了,这和类型别名不是一样嘛?type 和 interface 功能类似,差别在于,类型别名可以直接别名一个基础类型,而 Interface 不行。

interface Person {
  name: string;
}

const getPersonName: (person: Person) => void = (person) => {
  console.log(person.name);
};

const setPersonName: (person: Person, name: string) => void = (
  person,
  name
) => {
  person.name = name;
};

// 类型别名可以代表基础类型
type PersonType = number;

8.1 可选属性,只读属性,允许其他属性

  1. 如果有一个接口,必须要有 name,但是 age 可有可无(这种场景很常见),那么就需要用 ? 设置成可选属性。
  2. 另外,如果想要把某个属性设置成不可修改,添加 readonly 只读修饰符即可。
  3. 如果接口可以接收其他数据的话,例如 gender,则需要使用 propName 字段
interface Person {
    // name 必须要有,string 类型,只读
    readonly name: string;
    // age 可有可无,有的话必须是 number 类型
    age?: number;
    // 允许其他新添加的属性
    [propName: string]: any;
}

8.2 class 应用 interface

当类应用接口的时候,类里必须要有具备 interface 需要的的属性和方法。对面向对象编程有经验的看下面代码就懂了。

interface Person {
  name: string;
  age: number;
  getProfile(): string;
}

class User implements Person {
  name: string = "sjh";
  age: number = 18;

  getProfile(): string {
    return `${this.name} -- ${this.age}`;
  }
}

8.3 接口继承

例如,Teacher 继承 Person 后,除了本身的 teach 方法外,还继承了 Person 里的所有属性和方法。

interface Person {
  name: string;
  age: number;
  getProfile(): string;
}

interface Teacher extends Person {
  teach(): string;
}

class MyTeacher implements Teacher {
  name: string = "sjh";
  age: number = 18;

  teach(): string {
    return "teach";
  }
  getProfile(): string {
    return `${this.name} -- ${this.age}`;
  }
}

8.4 接口定义函数

接口可以用来定义函数:

interface SayHi {
  (word: string): string;
}

const say: SayHi = (word) => {
  return word;
};

9. class 类

JS 写类的传统方法是,使用构造函数定义并生成新对象,同时用构造函数的显式原型来添加类方法,这样的话,实例都能通过隐式原型访问当类方法。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

ES6 提供了更贴近与传统面向对象的写法,引入了 Class 的概念。ES6 里的类同样拥有继承其他类、重写父类方法、super 调用父类方法。

ES6 class 本质上是 ES5 构造函数的语法糖。

class Person{
}
console.log(typeof Person)                               //funciton
console.log(Person.prototype.constructor === Person)     //true

类的定义:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

TS 的类和 ES6 的很类似,但是添加了一些新特性。

9.1 类中的访问类型

访问类型有三种:private,protected,public。代表了三种访问权限。

使用方法如下代码所示:

class Person {
    public gender: string;
    private hand: Hand;
    protected wealth: Wealth;
    
    constructor(...) {
        ...
    }
}

如果成员变量没有写访问类型的话,默认访问类型为 public。

  1. public 允许类中的属性或方法在类的内部和外部被调用。
  2. private 只允许在类内部被使用,类的外部不能使用。比如自己的手和脚,只能自己调用。
  3. protected 允许在类内及继承的子类中使用。好比家里的传家宝,只给自己和自己的子孙用。

访问类型的意义:给不同的属性添加了权限,使得属性不被随意的调用,从而起到保护的作用。

9.2 constructor 构造器

使用方法

如果有 Person 类,每个 Person 类的实例是各不相同的,例如姓名,年龄,性别都有所不同。要在创建实例的时候就给实例赋予不同的属性,就需要用构造器。

class 在执行的时候,如果里边没有构造器,默认执行 constructor() {},即默认创建一个空对象。

使用方法:

class Person {
  public name: string;

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

const person = new Person("sjh");
console.log(person.name);

可以看到,上面的 Person 类里边分成两部分:

上面一部分是成员变量声明,相当于介绍一下类里边的成员。

下面一部分是构造器,指示了创建类实例的时候需要的参数和生成的实例。

// 简写形式,但是不推荐
class Person {
  constructor(public name: string) {}
}

const person = new Person("sjh");
console.log(person.name);
涉及继承情况

如果该类继承了其他父类,则必须在构造器里使用 super 函数调用一下父类的构造函数。

说的土一点,子类生成实例前必须要保证 super 能成功调用父类构造器创建父类实例。

super 的用法:

  • super(): 调用父类构造器,父类构造器不用传参。如果父类构造器需要参数,这样写没有传递参数,会报错。
  • super.foo() 或者 super.bar: 调用父类中的方法,属性也可以,看访问类型是否为 private。
class Person {
  public name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Teacher extends Person {
  public major: string;
  constructor(name: string, major: string) {
    super(name);
    this.major = major;
  }
}

const myTeacher = new Teacher("myCsTeacher", "front-end");

10. Setter 和 Getter

如果有属性,最原本的形式需要保护,需要暴露另外一种给外部。此时就需要用 getter,让外部获取经过处理后的数据。

同时,如果要给私有属性赋值,就需要 setter 进行处理。

class Person {
  private _name: string;
  constructor(_name: string) {
    this._name = _name;
  }
  get name() {
    return "from getter: " + this._name;
  }
  set name(name: string) {
    const realName = "real name: " + name;
    this._name = realName;
  }
}

const person = new Person("sjh");
console.log(person.name); // from getter: sjh

person.name = "ssjjhh";
console.log(person.name); // from getter: real name: ssjjhh

11. static 静态关键字

static 在方法或成员变量前,表示这个方法或变量是所有类的实例共用的,而非只有实例本身使用。

此时,使用 className.staticMethod() 来调用静态方法,或者 className.propertyName 来获取静态变量

使用 static 创造一个单例模式:

class Demo {
  // 存储生成的实例
  private static instance: Demo;
  // 规避 new 方法创建实例
  private constructor() {}

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

const demo1 = Demo.getInstance();
const demo2 = Demo.getInstance();
console.log(demo1 === demo2); // true

12. 抽象类

有种类,我们并不想它可以创造实例,但是希望以此做原形延伸出更多的类。

比如 Circle、Triangle、Square 三种图形,它们都有面积,所以肯定都有获取面积的方法,但是算出面积的公式都不一样,因此使用抽象类就很合适。

抽象类只能被继承,不能创建实例。

抽象类里面可以写具体的方法,也可以写抽象的方法。具体的方法写法和之前的一样,抽象的方法在方法名前加 abstract 关键字,且不需要写方法的实现内容。

abstract class Geom {
  // 感叹号表示非空断言,告诉 TS 该属性肯定会被赋值
  width!: number;
  // 非抽象方法
  getType() {
    return "Geometry";
  }
  // 抽象方法
  abstract getArea(): number;
}

如果有类继承了抽象类,这个类需要实现抽象类中的抽象方法。

class Circle extends Geom {
  width: number = 2;
  getArea(): number {
    return Math.PI * (this.width / 2) ** 2;
  }
}
抽象类与接口的异同

相同点

  1. 都不能直接实例化
  2. 接口的实现类和抽象类子类只有全部实现了接口或者抽象类中的抽象方法后才可以被实例化

不同点

  1. 抽象类里面可以有方法的实现,但是接口完全是抽象的,不存在方法的实现。同时,抽象类的成员变量需要有初始值或者构造器内赋值,接口里的属性不能有初始值。
  2. 子类只能继承一个抽象类,而类可以实现多个接口
  3. 抽象类里的成员变量和方法可以是 public 和 protected,但是接口中的所有属性和方法都是 public。
  4. 抽象类可以有构造器,但是接口不能有构造器

可以说,接口是一个比抽象类更抽象的存在,更像是给实现类加上约束。而抽象类是子类更笼统的描述。

就好比,麻雀叫做”鸟“没啥问题,但是不能叫做”会用两个翅膀飞“。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值