[ts] TypeScript语法学习笔记

JavaScript痛点,认识并安装Typescript

1. JavaScript本身存在很多缺点,如var作用域问题、数组类型不是连续的内存空间、没有类型检测机制等;

编程开发中错误出现的越早越好,IDE的优势就是代码编写过程中帮助我们发现错误,类型检测可以在代码编译期间发现错误

JavaScript项目一旦庞大后,其宽松的类型约束会造成很大安全隐患

14年,Facebook推出 flow 对JavaScript进行类型检查(vue2.x);同年,微软推出typescript(Angular, Vue3),都可以为JavaScript提供类型检测

2. typescript是用拥有类型的JavaScript超集,可以编译成JavaScript代码

JavaScript所有的特性typescript支持,不仅添加了类型约束,也进行了语法扩展,如枚举类型(Enum)、元组类型(Tuple)等; typescript实现新特性的同时,保持和ES标准的同步甚至领先;且typescript代码会被编译成JavaScript代码

// 全局安装
npm install typescript -g
// 查看版本
tsc --version

运行ts文件

使用ts-node

// 安装ts-node
npm install ts-node -g
// 安装ts-node的两个依赖tslib和@types/node包
npm install tslib @types/node -g
// 运行ts代码
ts-node math.ts

一、变量声明

typescript中定义变量需要制定标识符的类型,完整声明格式如下:

var/let/const 标识符: 数据类型 = 赋值

声明类型后,ts就会进行类型检测,声明的类型称为类型注解

若未添加数据类型,默认情况下进行赋值时,会将赋值的值的类型作为标识符的类型,称为类型推导

number类型

let age: number = 20
age = '20' // 不能将类型“string”分配给类型“number”。

// 多进制支持
let num1: number = 10
let num2: number = 0b10
let num3: number = 0o10
let num4: number = 0x10

boolean类型

let flag: boolean = false

flag = false
flag = 20 > 50

string类型

let msg1: string = 'aaa'
let msg2 = 'bbb'


// 个人习惯:默认情况下,如果能推导出对应的标识符的类型时,一般不加
const name = 'code'
const age = 17
const sex = '男'

let msg = `name: ${name} age: ${age} sex: ${sex}`
console.log(msg)

array类型

// 确定一个事实:names是一个数组类型,但是数组中存放的是什么类型的元素?
// 一个数组中在typescript开发中,最好存放的数据类型是固定的(string)
// 类型注解: type annotation
const names1: Array = [] // 不推荐(react jsx中是有冲突的 
) const names2: string[] = [] // 推荐 // 在数组中存放不同的类型是不好的习惯 names1.push('aaa') names1.push(111) // 类型“number”的参数不能赋给类型“string”的参数。

object类型

// const info: object = {
//   name: 'code',
//   age: 17
// }

// console.log(info.name); // 类型“object”上不存在属性“name”。

const info = {
  name: 'code',
  age: 17
}

console.log(info.name);

null和undefined类型

let n1: null = null
let n2: undefined = undefined

Symbol类型

// ES5中不可以在对象添加相同的属性名称
const obj = {
    a: 'a',
    a: 'a'
}
// 通过symbol来定义相同的名称,因为symbol函数返回的是不同的值
const bbb1 = Symbol('bbb')
const bbb2 = Symbol('bbb')

const info = {
  [bbb1]: '程序员',
  [bbb2]: '老师'
}

any类型

某些情况下无法确定变量类型,以及后续变化,此时可以使用any类型

  1. 可以对any类型的变量进行任何操作,如获取不存在的属性、方法;

  1. 给any类型的变量赋值任意类型的值

let msg: any = 1
msg = 'a'
msg = true
msg = {}

const arr: any[] = ['aaa', 333]

unknown类型

描述不确定的变量

  1. unknown类型只能赋值给any和unknown类型

  1. any类型可以赋值给任意类型

function foo() {
  return 'abc'
}

function bar() {
  return 111
}

let flag = true
// let result: string | number
// let result: any
let result: unknown
if(flag) {
  result = foo()
} else {
  result = bar()
}

let msg: string = result // 不能将类型“unknown”分配给类型“string”
let num: number = result // 不能将类型“unknown”分配给类型“number”

export {}

void类型

void指定一个函数没有返回值

function sum(num1: number, num2: number): void {
  console.log(num1 + num2);
}

never类型

表示函数不会返回值,如函数是一个死循环或抛出异常

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

  }
}

function bar(): never {
  throw new Error()
}

tuple类型

元组类型,元组每个元素都有自己的类型,根据索引值获取到的值可以确定对应的类型

数组中建议存放相同类型的元素

const info: [number, string] = [ 17, 'a']
const aaa = info[1]
console.log(aaa.length);
const bbb = info[0]
console.log(bbb.length); // 类型“number”上不存在属性“length”

函数的参数类型

ts允许指定函数的参数和返回值的类型

通常情况下可以不写返回值的类型(ts会根据return返回值自动推导返回类型)

function sum(num1: number, num2: number): number {
  return num1 + num2
}

匿名函数的参数

上下文类型,因为函数执行的上下文可以帮助确定参数和返回值的类型

const arr = ['a', 'b']
// item根据上下文的环境推导出来的,此时可以不添加类型注解
arr.forEach(item => {
  console.log(item);
});

对象类型

限定一个函数接受的参数是一个对象,可以使用对象类型

// {x: number, y: number}
function sum(point: {x: number, y: number}) {
  console.log(point.x);
  console.log(point.y);
}

sum({x: 1, y: 2})

可选类型

对象类型可以指定哪些属性是可选的,可在属性后加 ?:

// {x: number, y: number, z?: number}
function sum(point: {x: number, y: number, z?: number}) {
  console.log(point.x);
  console.log(point.y);
  console.log(point.z);
}

sum({x: 1, y: 2})
sum({x: 1, y: 2, z: 3})

// 一个参数一个可选类型的时候,它其实本质上是这个参数是:类型|undefined 的联合类型
function foo(msg?: string) {
  console.log(msg)  
}

联合类型Union Type

ts允许从现有类型中构建新类型;联合类型是由两个或多个其它类型组成的类型,表示可以是这些类型中的任何一个值,联合类型中的每一个类型被称为联合成员union's members

// number | string 联合类型
function printId(id: number | string) {
  // 使用联合类型的值时,需要特别小心
  if(typeof id === 'string') {
    console.log(id.toUpperCase());
  }
  console.log(id);
}

printId(1)
printId('2')
printId(true) //类型“boolean”的参数不能赋给类型“string | number”的参数。

类型别名

// type用于定义类型别名
type IDType = string | number | boolean
type PointType = {
  x: number,
  y: number,
  z: number
}

function printId(id: IDType) {

}

function printPoint(point: PointType) {
  
}

类型断言as

有时ts无法获取具体的类型信息,此时可以使用类型断言Type Assertions

ts只允许类型断言转换为更具体或不太具体的类型版本,此规则可防止不可能的强制转换

// 类型断言 as
const el = document.getElementById('why') as HTMLImageElement

el.src = 'url地址'

// Person 是 Student 的父类
class Person {

}

class Student extends Person {
  studying() {

  }
}

function sayHi(p: Person) {
  (p as Student).studying()
}

const stu = new Student()
sayHi(stu)

非空类型断言

!.

表示可以确定某个标识符是有值的,跳过ts编译检测

function printMsgLength(msg?: string) {
  console.log(msg.length); // “msg”可能为“未定义”。
  console.log(msg!.length);
}

printMsgLength('sadjfsdaf')
printMsgLength()

可选链的使用

ES11(ES2020)中增加的特性

操作符: ?.

作用:当对象的属性不存在时,会短路,直接返回undefined,如果存在才会继续执行

type Person = {
  name: string
  friend?: {
    name: string
    age?: number
  }
}

const info: Person = {
  name: 'code',
  friend: {
    name: 'bob'
  }
}


console.log(info.name)
console.log(info.friend?.name)

??和!!的作用

!!操作符:

将其它类型转换成boolean类型,类似于Boolean()的方式

??空值合并操作符:

当操作符左侧是null或者undefined时,返回其右侧操作数,否则返回左侧操作数

const msg = 'asdfsdf'

const flag = Boolean(msg)
console.log(flag);

const flag2 = !!msg
console.log(flag2);


let msg: string|null = null
const content = msg ?? '你好'

字面量类型literal types

将多个类型联合起来

let num: 123 = 123
// num = 321  // 不能将类型“321”分配给类型“123”

// 字面量类型的意义,就是必须结合联合类型
type Alignment = 'left' | 'right' | 'center'
let align: Alignment = 'center'
align = 'right'
align = 'left'

字面量类型

function request(url: string, method: 'GET' | 'POST') {}

const info = {
  url: 'https://www.baidu.com',
  method: "POST"
}

request(info.url, info.method) // 没办法将一个string赋值给一个字面量类型
request(info.url, info.method as 'POST')

字面量推理

info类型其实是{url: string, method: string},所有没办法将一个string类型赋值给一个字面量类型

function request(url: string, method: 'GET' | 'POST') {}

const options = {
  url: 'https://www.baidu.com',
  method: "POST"
}
request(options.url, options.method) // 类型“string”的参数不能赋给类型“Method”的参数。
request(options.url, options.method as 'POST')

类型缩小Type Narrowing

通过类似于 typeof num === 'number' 的判断语句,来改变typescript的执行路径;在给定的执行路径中,可以缩小比声明时更小的类型,此过程称之为 缩小

编写的 typeof num === 'number' 称之为 类型保护type guards

常见的类型保护有下列几种:

  • typeof

  • 平等缩小(===、!==)

  • instanceof

  • in:JavaScript运算符,用于确定对象是否具有带名称的属性,有返回true

// 1.typeof的类型缩小
type IDType = number | string
function printId(id: IDType) {
  if(typeof id === 'string') {
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

// 2.平等的类型缩小(=== == !== !=/switch)
type Direction = 'left' | 'right' | 'top' | 'bottom'
function printDirection(direction: Direction) {
  // 1.if判断
  // if(direction === 'left') {
  //   console.log(direction);
  // }

  // 2.switch判断
  // switch(direction) {
  //   case 'left':
  //     console.log(direction);
  //     break
  //   case 'right':
  //     console.log(direction);
  //     break
  //   case ...
  // }
}

// 3.instanceif
function printTime(time: string | Date) {
  if (time instanceof Date) {
    console.log(time.toUTCString());
  } else {
    console.log(time);
  }
}

// 4.in
type Fish = {
  swimming: () => void  // 表示没有参数,没有返回值
}

type Dog = {
  running: () => void
}

function walk(animal: Fish | Dog) {
    if('swimming' in animal) {
        animal.swimming()
    } else {
        animal.running()
    }
}

typescript函数类型

js中,函数是一等公民(可以作为参数,也可作为返回值进行传递)

// 1.函数作为参数时,在参数中如何编写类型
function foo() {}

type FooFnType = () => void
function bar(fn: FooFnType) {
  fn()
}

bar(foo)

// 2.定义常量时,编写函数的类型
type AddFnType = (num1: number, num2:number) => number
const add: AddFnType = (num1: number, num2:number) => {
  return num1 + num2
}

ts函数类型解析

(num1: number, num2:number) => void,代表一个函数类型

  • 接收两个参数的函数:num1和num2,并且都是number类型

  • 这个函数是没有返回值的,所以是void

ts中参数名称num1和num2是不可以省略的

function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) {
  return fn(n1, n2)
}

const res1 = calc(1, 2, function(a1, a2) {
  return a1 + a2
})
console.log(res1);


const res2 = calc(10, 20, function(a1, a2) {
  return a1 * a2
})
console.log(res2);

函数参数的可选类型

指定某个参数是可选的

// 可选类型是必须写在必选类型的后面的
// y -> undefined | number
function foo(x: number, y?:number) {}

foo(1, 2)
foo(2)

参数的默认值

// 必传参数 -> 有默认值的参数 -> 可选参数
function foo(x: number, y: number = 10) {
  console.log(x, y);
}

foo(10, 20)
foo(10)

参数的剩余参数

// function sum(num1: number, num2: number) {
//   return num1 + num2
// }

function sum(...nums: number[]) {
  console.log(nums);
}

sum(1, 2, 3) // [ 1, 2, 3 ]
sum(1, 2, 3, 4, 5, 6) // [ 1, 2, 3, 4, 5, 6 ]

函数的重载

ts中,可以编写不同的重载签名(overload signatures)来表示函数可以不同的方式进行调研

一般是编写两个或以上的重载签名,再去编写一个通用的函数以实现

// 函数的重载:函数的名称相同,但是参数不同的几个函数,就是函数的重载
function add(num1: number, num2: number): number
function add(num1: string, num2: string): string

function add(num1: any, num2: any): any {
  return num1 + num2
}

// 调用sum的时候,会根据传入的参数类型来决定执行函数体时,到底执行哪一个函数的重载签名
const res = add(1, 2)
const res2 = add('a', 'b')

联合类型和重载

案例:定义一个函数,传入字符串或数组,获取它们的长度

开发中能用联合类型实现,尽量用联合类型实现

// 1.联合类型实现
function getLength(args: string | any[]) {
    return args.length
}

// 2.函数重载
function getLength(args: string): number
function getLength(args: any[]): number

function getLength(args: any): number {
    return args.length
}

认识类的使用

ES5中需要通过函数和原型链来实现类和继承,从ES6开始引入了class关键字,方便了定义使用类

typescript支持class关键字,并且还对类的属性和方法等进行了静态类型检测

在面向对象中,任何事物都可以使用类的结构描述,类中包含有属性和方法

类的定义

使用class关键字定义类

可以声明一些类的属性;在类的内部声明类的属性以及对应的类型

  • 若类型没有声明,默认是any

  • 也可以给属性设置初始化值

  • 在默认的strictPropertyInitialization模式下面我们的属性是必须初始化的,如果没有初始化,那么编译会报错

  • 类有自己的构造函数constructor,通过new关键字创建一个实例,构造函数会被调用

  • 构造函数不需要任何值,默认返回当前创建出来的实例

  • 类中可以有自己的函数,定义的函数称之为方法

class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log(this.name + 'eating');
  }
}

const p = new Person('code', 17)
console.log(p.name);
console.log(p.age);
p.eating()

类的继承

面向对象一大特性——继承,继承不仅可以减少代码量,也是多态使用前提

使用extends关键字实现继承,子类中使用super访问父类

子类有自己的属性和方法,并且会继承Person的属性和方法,在构造函数中,可以通过super来调用父类的构造方法,对父类中的属性进行初始化

class Person {
  name: string = ''
  age: number = 0

  eating() {
    console.log('eating');
  }
}

class Student extends Person {
  sno: number = 0

  studying() {
    console.log('studying');
  }
}

const stu = new Student()
stu.name = 'code'
stu.age = 9
console.log(stu.name);
console.log(stu.age);
stu.eating()
class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log('Person eating');
  }
}

class Student extends Person {
  sno: number

  constructor(name: string, age: number, sno: number) {
    // super调用父类的构造器,必须写在前面
    super(name, age)
    this.sno = sno
  }

  eating() {
    console.log('Student eating');
    super.eating() // 调用父类方法
  }

  studying() {
    console.log('studying');
  }
}

const stu = new Student('code', 17, 1007)
console.log(stu.name);
console.log(stu.age);
console.log(stu.sno);
stu.eating()

类的成员修饰符

TS中,类的属性和方法支持三种修饰符:public、private、protected

  • public 在任何地方可见、公有的属性或方法,默认编写的属性就是public的

  • private 仅在同一类中可见、私有的属性或者方法

  • protected 仅在类自身及子类中可见、受保护的属性或方法

class Person {
  public name: string = 'code'
  private age: number = 17

  // 封装了两个方法,通过方法来访问和设置age
  getAge() {
    return this.age
  }
  setAge(val: number) {
    return this.age = val
  }
}

const p = new Person()
console.log(p.name);
// console.log(p.age); //属性“age”为私有属性,只能在类“Person”中访问。
console.log(p.getAge());
p.setAge(18)
console.log(p.getAge());
// protected:在类内部和子类中可以访问

class Person {
  protected age: number = 17
}

class Teacher extends Person {
  getAge() {
    return this.age // 子类中访问父类属性
  }
}

const p = new Teacher()
console.log(p.getAge());
// console.log(p.name); // 类型“Teacher”上不存在属性“name”。

只读属性readonly

若属性不希望外界任意修改,只希望确定值后直接使用,可以使用readonly

class Person {
  // 1.只读属性是可以在构造器中赋值,赋值之后就不可以修改了
  // 2.属性本身不可以修改,但如果它是对象类型,对象中的属性是可以修改的
  readonly name: string = 'code'
  age: number = 17
  readonly teacher?: Person

  constructor(name: string, teacher?: Person) {
    this.name = name
  }
}

const p = new Person('bbb', new Person('xxx'))
console.log(p.name);
console.log(p.teacher);
// p.name = 'aaa' // 无法为“name”赋值,因为它是只读属性。
// p.teacher = new Person('哈哈') // 无法为“teacher”赋值,因为它是只读属性。
if(p.teacher) {
  p.teacher.age = 19
}

getter/setter

一些私有属性不能直接访问,或某些属性想要监听它的获取getter和设置setter过程,可以用存取器

class Person {
  private _name: string

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

  // 访问器setter/getter
  set name(newName) {
    this._name = newName
  }
  get name() {
    return this._name
  }
}

const p = new Person('code')
p.name = 'haha'
console.log(p.name); // 'haha'

静态成员

通过类来访问的成员和方法,TS中通过static来定义

class Student {
  static time: string = '9:00'

  static attendClass() {
    console.log('学习')
  }
}

console.log(Student.time);
Student.attendClass()

抽象类abstract

继承是多态使用的前提

  • 在定义很多通用的调用接口时,通常会让调用者传入父类,通过多态来实现更加灵活的调用方式

  • 但父类本身可能并不需要对某些方法进行具体的实现,所有父类中定义的方法,可以定义为抽象方法

是什么是抽象方法?在ts中没有具体实现的方法(没有方法体),就是抽象方法

  • 抽象方法,必须存在于抽象类中

  • 抽象类是使用abstract声明的类

抽象类特点

  • 抽象类不能被实例化(不能通过new创建)

  • 抽象方法必须被子类实现,否则该类必须是一个抽象类

function makeArea(shape: Shape) {
  return shape.getArea()
}

abstract class Shape {
  abstract getArea()
}

class Rectangle extends Shape {
  private width: number
  private height: number

  constructor(width: number, height: number) {
    super()
    this.width = width
    this.height = height
  }

  getArea() {
    return this.width * this.height
  }
}

class Circle extends Shape {
  private r: number

  constructor(r: number) {
    super()
    this.r = r
  }

  getArea() {
    return this.r * this.r * 3.1415926
  }
}

const rectangle = new Rectangle(20, 30)
const circle = new Circle(10)

console.log(makeArea(rectangle));
console.log(makeArea(circle));

makeArea('aaa') // 类型“string”的参数不能赋给类型“Shape”的参数。
makeArea(new Shape()) // 无法创建抽象类的实例。

你可能感兴趣的:(typescript,typescript)