一、TS抓紧学起来
- TS是JS的超集,是建立在JS上的一门语言。
- TS可以使用JS之外的扩展语法、面向对象、静态类型。
- TS基础语法 -- 爬虫功能开发 -- TS语法进阶 -- 项目接口开发 -- TS高级语法 -- 项目代码重构 -- 项目前端开发 -- 总结
包含知识点:静态类型、类型注解、类型推断、泛型、类型定义文件、模块化、打包编译、装饰器、Metadata(元数据)、设计模式
二、TS基础语法
1. TS的定义
推荐使用VSCode编辑器。因为TS和VSCode都是微软推出的。VSCode做了很多对TS的适配。
参考文档:
定义:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.TS是JS的超集,被编译为JS之后才可以运行。TS是静态代码类型。
// ts文件
let a = 123
a = '123' // 不能将类型“"123"”分配给类型“number”。因为其为静态类型,因此不能从number类型变为string类型。
标准写法
let a: number = 123
interface Person {
name: string
}
const teacher: Person = {
name: 'Fruit Bro'
}
2. TS的优势
interface Point {
x: number,
y: number,
}
function tsDemo (data: Point) {
return Math.sqrt(data.x ** 2 + data.y ** 2)
}
tsDemo({x: 1, y: 2})
优势:
- 开发过程中,发现潜在问题。
- 更友好的编辑器自动提示。
- 代码语义更清晰易懂。
3. TS基础环境搭建
参考文档:
// 安装TS
npm install typescript -g
tsc demo.ts 就生成了demo.js文件
node demo.js // 运行生成的文件
// 转化工具 简化上述过程
npm install -g ts-node
// 运行
ts-node demo.ts
4. TS静态类型理解
// count为number类型之后,会具备number类型的所有属性和方法
const count: number = 2020
count.提示的都是number类型所对应的方法,如下图:
// 自定义类型
interface Point {
x: number,
y: number,
}
const point: Point = {
x: 1,
y: 2
}
因此point变量具备Point所有的属性和方法。
总结:我们看到一个变量是静态类型,不仅仅意味着这个变量的类型不能修改,还意味这个变量的属性和方法已基本确定。
5. TS基础类型和对象类型
- 基础类型
let count: number = 123
const personName: string = 'style'
还有null, undefined, symbol, boolean, void
- 对象类型
const teacher: {
name: string,
age: number,
} = {
name: 'fruit',
age: 18,
}
// 数组
const numbers: number[] = [1, 2, 3]
// 类
class Person {}
const fruit: Person = new Person() // fruit必须是个person类
// function, 返回值为number的函数
const getTotal: () => number = () => {
return 123
}
6. TS类型注解(type annotation)和类型推断(type inference)
let count: number;
如上所示,显式声明变量类型的写法,称为类型注解。
let countInfernce = 123
const firstNumber = 1
const secondNumber = 2
const total = firstNumber + secondNumber
在我们写的代码中,变量并没有使用类型注解,但并未报错,就是因为TS进行了类型推断。
如果TS会自动分析变量类型,我们就什么都不需要做了。 如果TS无法分析变量类型的话,我们就需要使用类型注释。
function getTotal(firstNumber: number, secondNumber: number) {
return firstNumber + secondNumber;
}
const total = getTotal(1, 2) // 此时就不需要写类型注解了
如上所示,此时是需要些类型注解的。
7. 函数相关类型
function add(first: number, second: number): number { // 定义返回类型
return first + second
}
const total = add(1, 2)
// 如果不定义返回类型,如下写法不会报错
function add(first: number, second: number) { // 未定义返回类型
return first + second + '' // 返回字符串
}
const total = add(1, 2)
function sayHello (): void { // 无返回值void
console.log('hello')
}
TS中常用的返回值类型为:JSX.Element
// never:这个函数永远不会执行到最后,如下两种情况
function errorEmitter (): never {
throw new Error()
console.log('123')
}
function errorEmitter (): never {
while(true) {}
}
解构赋值的类型写法:
情况1:
function add({ first, second }: {first: number, second: number}): number {
return first + second
}
const total = add({first: 1, second: 2})
情况2:
function getNumber ({first}: {first: number}) {
return first
}
const count = getNumber({first: 1})
8. TS基础语法
基础类型 boolean、number、string、void、symbol、undefined、null
对象类型 {}、function、[]、class
函数写法1
const func = (str: string): number => {
return parseInt(str, 10)
}
函数写法2
const func: (str: string) => number = (str) => {
return parseInt(str, 10)
}
Date类型。
const rawData = '{"name": "fruit"}'
const newData = JSON.parse(rawData) // JSON.parse返回的内容并不能帮助TS推断newData的类型
如下图,newData为any
interface Person {
name: string
}
const rawData = '{"name": "fruit"}'
const newData: Person = JSON.parse(rawData)
更多类型
let temp: number | string = 123 // temp变量有可能为number或string
temp = '456'
9. TS数组和元组
元组:规定了每一个元素的类型。数量个数有限的数组,同时每一项的类型又是固定的形式。
数组例子:
数组有两种定义方式
方法一
let list: number[] = [1, 2, 3]
方法二
let list: Array<number> = [1, 2, 3]
基础类型
// 全部为number类型
const numberArr: number[] = [1, 2, 3]
// 为number或string
const numberArr: (number | string)[] = [1, '2', 3]
// 全部为string类型
const stringArr: string[] = ['a', 'b', 'c']
// 全部为undefined类型
const undefinedArr: undefined[] = [undefined]
对象类型
const objectArr: {name: string, age: number}[] = [{name: 'fruit', age: 18}]
也可以用类型别名
// type alias 类型别名,使用type关键字来定义
type User = {name: string, age: number}
const objectArr: User[] = [{name: 'fruit', age: 18}]
// 使用class
class Student {
name: string;
age: number;
}
const objectArr: Student[] = [
new Student(),
{ // 数据结构与Student保持一致也可以
name: 'fruit',
age: 18
}
]
元组例子:
// 元组 tuple
const teacherInfo: [string, string, number] = ['Fruit', 'male', 18];
元组经常用于CSV、Excel等类型的文件中
// 用元组处理CSV文件
const studentList: [string, string, number][] = [
['fruit', 'male', 18],
['bro', 'female', 26],
['jhon', 'female', 28],
]
10. TS的Interface接口
interface Person {
name: string;
}
const getPersonName = (person: Person) => {
console.log(person.name);
};
const setPersonName = (person: Person, name: string) => {
person.name = name;
};
用类型别名也可以
type Person = {
name: string;
}
const getPersonName = (person: Person) => {
console.log(person.name);
};
const setPersonName = (person: Person, name: string) => {
person.name = name;
};
interface接口与type(类型别名)的区别: type Person = string,type可以直接代表string,但interface只能代表一个对象或函数,没办法代表基础类型。
总结如下:
相同点: 1. 都可以描述一个对象或者函数 2.都允许用extends拓展
不同点: 1. type可以声明基本类型别名、联合类型、元组类型等 2. type 语句中还可以使用 typeof 获取实例的 类型进行赋值 3. 而inteface被多次声明时,能够声明合并
规范:能用接口的尽量用接口表示,实在没法表示再用类型别名。
关于interface和type区别的详细介绍可参考:Typescript 中的 interface 和 type 到底有什么区别
interface Person {
readonly name: string; // 只读
age?: number; // 可选属性
[propName: string]: any; // 除了name和age外,还可以有其他属性,此属性名为字符串类型,属性值为任何类型
}
const getPersonName = (person: Person): void => {
console.log(person.name);
};
const setPersonName = (person: Person, name: string): void => {
person.name = name; // name为只读后,将不能赋值
};
const person = {
name: 'fruit',
}
getPersonName(person)
setPersonName(person, 'bro')
第一种
const person = {
name: 'fruit',
sex: 'male',
}
getPersonName(person)
第二种
const person = {
name: 'fruit',
sex: 'male',
}
getPersonName({
name: 'fruit',
sex: 'male', // 此种写法会报错,如下图
})
接口中有方法
interface Person {
name: string;
age?: number;
[propName: string]: any; // 除了name和age外,还可以有其他额外属性,此属性名为字符串类型,属性值为任何类型
say(): string; // 接口中有say方法,返回值为string类型
}
const getPersonName = (person: Person): void => {
console.log(person.name);
}
const person = {
name: 'fruit',
sex: 'male',
say() { // 方法
return 'say hello' // 返回值
}
}
getPersonName(person)
类
interface Person {
name: string;
age?: number;
[propName: string]: any;
say(): string;
}
class User implements Person { // 类User应用Person接口
}
Person接口中name和say方法是必传的,类里面必须具备接口的属性。
下面的写法是正确的
class User implements Person {
name = 'fruit'
say() {
return 'hello fruit'
}
}
接口的继承
interface Person {
name: string;
age?: number;
[propName: string]: any;
say(): string;
}
interface Student extends Person { // Student继承Person,并增加自己的方法study
study(): string
}
1、ts类中只能继承一个父类 2、ts类中可以实现多少接口,使用(,)号分隔 3、ts接口中可以继承多个接口,使用(,)号分隔
interface InterfaceThree extends InterfaceOne,InterfaceTwo {
}
函数的类型声明定义
interface SayHi { // 函数的类型声明
(word: string): string // 接收string类型的参数,同时返回值为string
}
const say: SayHi = (word: string) => {
return word
}
总结:
- interface和type相类似,但不完全一致。
- readonly 只读属性、?可选参数、propName 额外属性、say()方法属性
- extends 接口继承
- 接口可直接定义具体的方法类型
- implements 用来做class类的属性约束
- 接口就是在我们开发的过程中帮助我们做语法提示的工具,编译后接口和类型全部剔除掉,并不会变成js代码
11. TS中类的定义与继承
普通类
class Person1 {
name = 'fruit';
getName() {
return this.name;
}
}
const person2 = new Person1();
console.log(person2.getName()); // fruit
普通类继承
class Teacher extends Person1 {
getTeacherName() {
return 'Bro';
}
}
const teacher1 = new Teacher();
console.log(teacher1.getName()); // fruit
console.log(teacher1.getTeacherName()); // Bro
类的重写
class Teacher extends Person1 {
getTeacherName() {
return 'Bro';
}
getName() { // 重写getName
return 'Fruit'
}
}
const teacher1 = new Teacher();
console.log(teacher1.getName()); // Fruit
console.log(teacher1.getTeacherName()); // Bro
super关键字
class Teacher extends Person1 {
getTeacherName() {
return 'Bro';
}
getName() { // 重写getName
return super.getName() + 'bro' // 使用super关键字,调用父类的方法
}
}
const teacher1 = new Teacher();
console.log(teacher1.getName()); // fruitbro
super一般用来做什么:当一个类把父类的方法覆盖(重写)掉之后,此时还想调用父类的方法,此时可通过super来调用。
12. 类的访问类型和构造器
- 访问类型:
public、protected、private
public:允许在类class的内、外(new出的实例中)被调用。
private: 允许在自身的类class内被使用。
protected: 允许在类内及继承的子类中使用。
// private protected public 访问类型
class Person {
protected name: string; // 不写默认访问类型为public
public sayHi() {
console.log('hi');
}
}
class Student extends Person {
public sayBye() {
this.name;
}
}
const person = new Person();
person.name = 'fruit'; // name为protected,此时调用会报错
console.log(person.name);
person.sayHi();
constructor构造器
constructor在new实例化的时候会被自动执行
constructor语法:
写法1
// constructor
class Person {
// 传统写法
public name: string;
constructor(name: string) {
this.name = name;
}
}
// constructor在new实例化的时候会被自动执行
const person = new Person('fruit');
console.log(person.name);
写法2
class Person {
// 简化语法
constructor(public name: string) {}
}
// constructor在new实例化的时候会被自动执行
const person = new Person('fruit');
console.log(person.name);
constructor在new实例化的时候会被自动执行
类的继承
子类要手动调用一下父类的构造器,还要按父类构造器的要求将参数传过去
class Person {
constructor(public name: string) {}
}
class Student extends Person {
constructor(public age: number) {
super('fruit'); // 子类要手动调用一下父类的构造器,还要按父类构造器的要求将参数传过去
}
}
const student = new Student(28);
console.log(student.age); // 28
console.log(student.name);// fruit
- 为什么我们要对一个类设置访问类型呢?访问类型的作用是什么呢?
13. 静态属性,Setter和Getter(重要)
Getter、Setter
class Person2 {
// 一般private属性会写一个_
constructor(private _name: string) {}
get name() {
// getter写法,可以确保name的安全性,如:对name进行加密
return this._name + 'bro';
}
set name(name: string) {
// setter写法。对name进行解析
const realName = name.split(' ')[0];
this._name = realName;
}
}
const person = new Person2('fruit');
console.log(person.name); // fruitbro 执行getter,间接调用name属性
person.name = 'Fruit Bro';
console.log(person.name); // Fruitbro
单例模式:一个类,只允许通过这个类,获取一个这个类的实例
一般情况一下,一个类可以生成很多个实例,如下:
class Demo {
}
const demo1 = new Demo()
const demo2 = new Demo()
class Demo {
private constructor() {}
}
// 限制外部不允许以new的方式创建实例
// new的时候要执行constructor,但此时它是private,因此是不允许的,就通过这种方式规避了new的情况
const demo1 = new Demo() // 此时new会报错
创建单例的写法:
class Demo {
// 在Demo上创建instance属性
private static instance: Demo;
private constructor(public name: string) {}
// static是指直接把这个方法挂在类上,而不是类的实例上
static getInstance() {
// instance为undefined时进行赋值
if (!Demo.instance) {
Demo.instance = new Demo('Fruit Bro');
}
return Demo.instance; // 若已创建过,则直接返回已创建的实例
}
}
// 因此就可以直接调用了
const demo1 = Demo.getInstance();
const demo2 = Demo.getInstance();
console.log(demo1 === demo2); // true
console.log(demo1.name); // Fruit Bro
console.log(demo2.name); // Fruit Bro
以上方法就构造了一个单例。核心思想:只让new方法执行一次!
readonly只读属性的用法:
// readonly
class Person {
public readonly name: string
constructor(name: string) {
this.name = name
}
}
const person3 = new Person('Fruit');
person3.name = 'Bro' // 此时赋值会报错
console.log(person3.name)
14. 抽象类
- 定义抽象类 abstract,抽象类被继承,不能直接用new实例化
- 抽象类主要是把一些通用性的东西做一下封装
- 抽象的实现方法,必须在继承子类中实现
abstract class Geom {
width: number;
getType() {
return 'Geom';
}
abstract getArea(): number; // 抽象的实现方法,必须在继承子类中实现
}
// Circle、Square、Triangle都有getArea方法,则可以将其抽象出出来
class Circle extends Geom {
getArea() { // // 抽象的方法,必须在继承子类中实现
return 123;
}
}
class Square {}
class Triangle {}
interface接口的抽象及其继承
// 接口和抽象类有些类似
interface Person { // 提出公共接口
name: string
}
interface Teacher extends Person { // 接口继承
teacherAge: number;
}
interface Student {
age: number;
}
const teacher = {
name: 'fruit'
};
const student = {
name: 'bro',
age: 18
};
const getUserInfo = (user: Person) => { // 只使用一个name属性就用Person接口,而不用(Teacher | Student),写法更简单
console.log(user.name);
};
getUserInfo(teacher);
getUserInfo(student);