TypeScript教程

注:本文仅对ts中相对js不一样的地方做了笔记📒哟~想看js教程请挪步👉🏻

js基础教程

简介

   Typescript是以javascript为基础构建的语言(是JavaScript的超集),可以在任意支持Javascript的平台中执行,但是它不能直接被js解析器执行,需要通过编译成js语言才可以解析;

安装Typescript

typeScript的命令行工具安装方法如下:

npm install typescript -g

该命令会在全局环境下安装tsc命令,编译一个typeScript文件(后缀为.ts)需要使用命令:

tsc 文件名

使用上述命令编译后,会自动生成对应的.js文件;

也可以使用vscode编辑器自动编译:

  • 首先使用在项目文件夹下执行命令,生成对应的配置文件tsconfig.json
tsc --init 

并将tsconfig.json文件中的outDir配置为对应的输出路径;

  •  在vscode编辑器中选择终端--运行任务,选择typescript并监视,

即可实现监视ts文件的变化,自动生成js文件;

类型声明

   类型声明是TS最重要的一个特点,通过类型声明可以指定TS中变量(形参、实参)的类型,在指定类型之后,当为变量赋值时,ts编译器会自动检查值是否符合类型声明,符合才会进行赋值;

let 变量: 类型; // 声明
let 变量: 类型 = 值; // 声明并赋值
function fn(参数: 类型, 参数: 类型): 类型 {
    
}
类型例子描述
number1 23 -23任意数字
string'hi' 'welcome'任意字符串
booleantrue false布尔值
字面量12 ‘string’ {} []限制变量的值就是该字面量的值
any*任意类型
unknown*任意类型
void空值(undefined)没有值(或undefined),一般用于声明函数返回值
never没有值不能是任何值
object{name: '小明'}任意的js对象
array[2, 3, 4]任意的js数组
tuple[4, 5]固定长度的数组,新增数据类型
enumenum{male, female}枚举,新增数据类型

    注意当声明类型为any时,相当于对该变量关闭了Ts类型检测,但是在将一个any类型的变量a赋值给一个非any变量b时,会将b也变成any类型(容易造成变量类型混乱,所以要慎用any);若变量声明时未定义类型时,默认是any类型(隐式的any);一般用unknown代替any使用,它相当于一个安全类型的any,它不可以随意赋值给其他类型变量;

never表示永远不会返回结果,可以用于函数抛错:

function fn(): never {
    throw new Error('抛错喽')
}

{}用于指定对象中可以包含哪些属性:

let person : {
    name: string,
    class: string,
    project?: string   
} // 声明person变量

person = {
    name: '小明',
    class: '一年级'
} // 变量赋值

let dog : {
    name: string,
    [propName: string]: any

} // 声明dog变量

dog = {
    name: '汪汪',
    gender: '男',
    age: 1
} // 增加了number类型的age与string类型的gender 


声明对象时,在属性名后面加上?表示该属性是可选的

声明对象时,加上[propName: string]: any 表示可定义任意类型的对象(propName为属性名,属性名为string类型)

声明数组时,可以在变量前加上类型,表示数组内都是什么类型的数据,或者通过Array<类型>表示;

 类型[];
 Array<类型> // 泛型
let arr: number[] = [123,456]; // 定义一个数字数组
let arr2: Array<string> = ['345','678']; // 使用泛型定义一个字符串数组
let e: string[]; // 表示e是字符串数组
let f: number[]; // 表示f是数字数组
let g: Array<string> // 表示g是字符串数组

元组是Ts新增的数据类型,元组就是固定长度的数组;元组中的元素不必保持类型一致

语法: [类型, 类型, 类型]
示例: 
let h :[string, number, string];
h = ['hi', 1, 'num']

添加内容的时候,必须是所定义的内容:

let h :[string, number, string];
h = ['hi', 1, 'num']
h.push(321)
h.push(true) // Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
console.log(h); // ['hi', 1, 'num', 321, true]

枚举一般用于对已知的数据进行列举:如果枚举中的标识符没有赋值,则值一般为下标,如下例中Male即为0

enum Gender {
    Male,
    Female
} // 定义Gender为枚举类型


let i: {
    name: string,
    gender: Gender
} // gender属性为Enum类型

i = {
    name: '小明',
    gender: Gender.Male
}

 外部枚举是使用declare enum定义的枚举类型:

declare enum Directives  {
    Up,
    Down,
    Left,
    Right
}

let directives = [Directives.Up, Directives.Down, Directives.Left, Directives.Right]

declare定义的类型只会用于编译时的检查,在编译结果中会被删除;

即上述的编译结果为:

let directives = [Directives.Up, Directives.Down, Directives.Left, Directives.Right];

外部枚举与声明语句一样,一般用于声明文件中;

同时使用declare与const也是可以的;

declare const enum Directives {
    Up,
    Down,
    Left,
    Right
}
let directives =  [Directives.Up, Directives.Down, Directives.Left, Directives.Right]

编译结果为

let directives = [0 /* Directives.Up */, 1 /* Directives.Down */, 2 /* Directives.Left */, 3 /* Directives.Right */];

联合类型

可以使用 | 来连接多个类型(联合类型),联合类型表示可以取值为多种类型中的一种

let age: number | string 
age = 18;
age = '21';
age = true; // 报错 Type 'boolean' is not assignable to type 'string | number'.

当ts不确定一个联合类型的变量到底是哪个类型的时候,只能访问此联合类型的所有类型中共有的属性或方法 ;

function searchAge(age: number | string) {
    console.log(age.toString()); // 访问公有方法
    console.log(age.length); // 报错:Property 'length' does not exist on type 'string | number'.
    // Property 'length' does not exist on type 'number'.ts(2339)
}

当再次给被联合声明定义的变量赋值后,则此时该变量的类型就会被确定:

let age: number | string;
age = 12 // 此时age确认为number类型
console.log(age.length); // 报错Property 'length' does not exist on type 'number'.

类型断言

类型断言有两种方式:使用as或者使用<>在变量前声明;类型断言用于告诉解析器变量的实际类型;

变量 as 类型
<类型>变量

第二种方式除了作为类型断言外,还可以表示一个泛型,所以在使用类型断言时,一般推荐第一种方式(使用as关键字) 

类型推论

如果没有明确的指定类型,那么ts就会依据类型推论的规则推断出一个类型;

如:

let firstName = 'xiao';
firstName = 12 // 报错Type 'number' is not assignable to type 'string'.

就会将firstName断言成string类型,相当于如下代码:

let firstName: string = 'xiao';
firstName = 12 // 报错Type 'number' is not assignable to type 'string'.

即在定义变量的时候同时赋值,则默认该变量为其赋值类型;

let firstName
firstName = 12 
firstName = '12'
firstName = {}

 若只定义了变量未赋值,则默认类型是any,可任意赋值;上述代码相当于

let firstName: any
firstName = 12 
firstName = '12'
firstName = {}

类型别名

类型别名用来给一个类型起个新名字,使用关键字type可以定义类型别名;

type str = string; // 将string类型重命名为str

类型别名常用于定义联合变量:

type nameProp = string | number

let gender: nameProp = 'male';
let age: nameProp = 12

字符串字面量类型

字符串字面量类型用来约束取值只能是某几个字符串中的一个;也是使用type关键字进行定义;

type strProp = 'students' | 'teachers' | 'schools'
let stu: strProp = 'students';
let tea: strProp = 'teachers'

接口

在面向对象的编程中,接口是一种规范的定义。规范一个类中应该包含哪些属性和方法,同时接口也可以当做类型声明去使用;

接口可以在定义类的时候去限制类的结构接口中的所有属性都不能有实际的值,只能定义对象的结构;并且在接口中所有的方法都是抽象方法(在类中都要实现)

接口是行为的抽象,而具体怎么行动需要由类去实现(implement),当然也可以对【对象的形状】进行描述~

接口可以继承接口,实现接口的子类也可以同时实现多个接口;

用接口定义对象

interface myInterface {
    name: string
    age: number
}

const obj: myInterface = {
    name: '小明',
    age: 12,
} // 接口作为类型声明来使用

 定义了接口类型的变量,其属性和方法必须保证与接口定义的相同不能多也不能少);

interface myInter {
    name: string;
    sayHello(): void
}
// 用类实现一个接口,即使类满足接口的要求
class MyClass implements myInter {
    name: string,
    constructor(name: string) {
        this.name = name
    }
    sayHello() {
        console.log('大声呼喊')
    }
}

用接口定义数组

接口也可以用来定义数组,只要将属性设置为number类型即可:

interface Arr {
  [index: number]: string // 定义字符串数组
}
let arr4: Arr = ['23', '34', '45']

用接口定义函数

函数定义有两种形式:函数声明和函数字面量形式

// 函数声明形式--命名函数
function searchText(day: string, text: string): string {
  return text + day
}

// 函数表达式形式--匿名函数
const searchText = function(day: string, text: string): string {
  return text + day
}

// 函数表达式形式--匿名函数之完整写法
const searchText: (day: string, text: string) => string = function(day: string, text: string): string {
  return text + day
}

使用接口定义: 

interface searchFuc {
  // 格式为(参数: 参数类型, .... , 参数:参数类型): 函数返回值类型
  (day: string, text: string): string
}

const searchText: searchFuc = function(day: string, text: string): string {
  return text
}

接口继承

接口也可以继承其它的多个接口

interface ISing {
  sing(): void
}
interface IDance {
  dance(): void
}
interface IAction extends ISing, IDance{
  aciton(): void
}
// IAction 不仅有action方法,还会继承到sing与dance方法

接口也可以继承类:

class action {
  name: string
  constructor(name: string) {
    this.name = name
  }
  sing(): void {
    console.log('唱歌');
  }
  dance(): void {
    console.log('跳舞');
  }
}

interface animal extends action {
  age: number
}
// animal接口中具有属性name与age,以及sing和dance方法

let dog: animal = {
  name: 'wangwang',
  age: 2,
  sing() : void {
    console.log('狗会汪汪叫');
  },
  dance(): void {
    console.log('狗也会乱跳');
  }
}

可选变量与任意变量

在定义对象时,有时会 遇到一些属性不一定是每个对象都必有的属性,该变量为可选变量

同时,在定义对象时,我们也会遇到一开始定义时并不确定都有神马变量的情况,该变量成为任意变量

interface Person {
    name: string;
    age: number;
    gender?: string; // 可选属性gender
    [propName: string]: any // 定义可添加任意类型的任意属性
}

let person: Person = {
    name: '123',
    age: 12,
    hobby: 'reading'
}

一旦定义了任意变量,则确定属性与可选属性的类型都必须是它的类型的子集

interface Person {
  name: string;
  age?: number; // 报错:Property 'age' of type 'number | undefined' is not assignable to 'string' index type 'string'.
  [propName: string]: string // 定义任意属性皆为string类型
}
interface Person2 {
  name: string;
  age: string;
// name与age都必须要是string类型
  [propName: string]: string 
}

一个接口中只能定义一个任意属性,若任意属性包含多个类型,则可以在任意属性中使用联合类型:

interface Person2 {
  name: string;
  age: number;
  [propName: string]: string | number
}

let person: Person2 = {
  name: '张张',
  age: 34
}

则name就可以和age设置成不同类型的变量~ 

只读属性

若希望对象中一些字段只能在创建的时候被赋值,则可以用readonly定义只读属性;

interface Ideas {
  readonly title: string,
  time: string
}
let ideas: Ideas = {
  title: '这是个秘密',
  time: '20230523'
}

ideas.title = '看看能修改不' // Cannot assign to 'title' because it is a read-only property.ts

Ts中除了实现了所有Es6中的类的功能以外,还添加了一些新的用法~

class animal {
  // 要先声明类型
  name: string 
  age: number
  constructor(name: string, age: number) {
    this.name = name
     this.age = age
  }
  say() {
    console.log('我是' + this.name);
  }
}
let dog = new animal('wangwang', 12);
dog.say() // 我是wangwang

 使用extends进行继承(与js一样);

存取器

使用getter和setter可以改变属性的赋值和读取行为:

在读取时会调用类中的setter方法,赋值(修改)时会调用类中的getter方法;

class Flower {
  name: string
  constructor(name: string) {
    this.name = name
  }
  get Name() {
    console.log('数据被获取了');
    return this.name
  }
  set Name(newValue: string) {
    console.log('数据被改变了', newValue);
    this.name = newValue
  }
}
let meigui = new Flower('玫瑰花')
console.log(meigui.name); // 玫瑰花
console.log(meigui.Name); // 数据被获取了
meigui.Name = '月季花' // 数据被改变了 月季花
console.log(meigui.name);  // 月季花

此处补充下Ts中的private/protected/public/readonly修饰符:

public修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public的;

private 修饰的属性或方法是私有的,只在当前类中可见,对实例对象以及子类都是不可见的

class Animal { 
  private run() {
      console.log('run')
  }
  protected move() {
      this.run()   //对当前类是可见的
      console.log('move')
    }
  _run() {
      this.run()
      this.move() //对当前类是可见的
      console.log('_run')
  }
}
const a = new Animal()
// a.run() Property 'run' is private and only accessible within class 'Animal'.

//子类
class Dog extends Animal { 
  bark() {  
  // this.run() 对子类以及实例是不可见的Property 'run' is private and only accessible within class 'Animal'.
    console.log('汪!')
  }
}
const dog1 = new Dog()
// dog.run()  属性“run()”为私有属性,只能在类“Animal”内部访问。

protected 修饰的属性或方法是受保护的,与private的区别是它在其子类中是可以被访问的但是在父类或子类的实例中都是不能被访问的

class Flower {
  public name: string
  private age: number
  protected count: number
  public constructor(name: string, age: number, count: number) {
    this.name = name
    this.age = age
    this.count = count
  }
  protected getName() {
    console.log('获取name');
  }
  private getAge() {
    console.log('获取age');
  }
}

class meiGui extends Flower {
  constructor(name: string, age: number, count:number) {
    super(name, age, count)
  }
  showMeigui() {
    console.log(this.name + this.age + this.count);
    // 此处子类中仅可以访问name与count(protected),age不能被访问
  }
}
class Animal { 
    protected move() {
      console.log('move')
    }
    run() {
      this.move()
      console.log('run')
    }
  }
  const b = new Animal() 
//   b.move()  通过父类实例对象访问不到move()这个方法的,只能在类“Animal”及其子类中访问。

//子类继承父类
class Dog extends Animal { 
    bark() {  
      this.move()
      console.log('汪!')
    }
  }
  const dog = new Dog()
//   dog.move() 通过子类实例对象也访问不到move()这个方法的,只能在类“Animal”及其子类中访问。

readonly为只读属性,用来防止在构造函数之外对属性进行赋值(只能在构造函数里进行赋值);

class Y {
  readonly age: number
  constructor(age: number) {
    this.age = age // 在构造函数中可以访问
  }
  setAge() {
    console.log(this.age); // 可以读
    this.age = 15 // 不可以改,Cannot assign to 'age' because it is a read-only property.
  }
}

以上四个修饰符都可以直接放在参数前面,表示创建并且初始化:

class Y {
  constructor(readonly age: number) {
    this.age = age // 在构造函数中可以访问
  }
  setAge() {
    console.log(this.age); // 可以读
    this.age = 15 // 不可以改,Cannot assign to 'age' because it is a read-only property.
  }
}

抽象类

abstract用于定义抽象类和其中的抽象方法

①抽象类是不允许被实例化的

abstract class Y {
  age: string
  constructor(age: string) {
    this.age = age // 在构造函数中可以访问
  }
  abstract setAge(): string
}

let y = new Y('12') // Cannot create an instance of an abstract class

②抽象类中抽象方法必须由子类实现

abstract class Y {
  age: string
  constructor(age: string) {
    this.age = age
  }
  abstract setAge(): string // 不能有具体实现
}

class y extends Y {
  constructor(age: string) {
    super(age)
  }
  setAge(): string {
    console.log('拿到数据了'); // 在子类中必须实现抽象类中的方法
    return this.age
  }
}

类与接口

接口也可以对类的一部分行为进行描述

实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口,用implements关键字来实现,这个特性大大提高了面向对象的灵活性;

interface ISing {
  sing()
}

class per implements ISing {
  sing() {
    console.log('人会唱歌');
  }
}

class ani implements ISing {
  sing() {
    console.log('动物也会唱歌');
  }
}

类也可以同时实现多个接口,

interface ISing {
  sing()
}
interface IDance {
  dance()
}
class per implements ISing, IDance {
  sing() {
    console.log('人会唱歌');
  }
  dance() {
    console.log('人会跳舞');
    
  }
}

泛型

泛型是解决类、接口、方法的复用性以及对不确定数据类型的支持

用法:在函数名称/接口名称/类名称的后面添加< > (尖括号),尖括号中添加类型变量

// 泛型类
function fn<T>(a: T): T {
//参数a和function返回值皆是泛型T
}

function fn2<T, K>(a: T, b: K): T{
    return a
}
// 参数a和function返回值皆是泛型T,参数b是泛型K

示例:定义一个函数,传入两个参数,第一个参数是数据(数据类型不定),第二个参数是数量,根据数量产生对应个数的数据,存放在一个数组里面; 

// 不确定传入数据的类型,因此将data设为泛型,函数返回值也为泛型数组
function setArr<T>(data: T, count: number): T[] {
  const arr: T[] = [] 
  for (let index = 0; index < count; index++) {
    arr.push(data)
  }
  return arr
}
let result = setArr(123, 3); // 走了类型断言
console.log(result); //  [123, 123, 123]
let result2 = setArr<string>('123', 3); // 使用的时候确定类型
console.log(result2); //  ['123', '123', '123']

示例:定义一个泛型接口

interface Ifun {
  <T>(data: T, num: number): Array<T>
}

let saySch: Ifun = function<T>(data: T, num: number): T[] {
  let arrs: T[] = []
  for (let index = 0; index < num; index++) {
    arrs.push(data)
  }
  return arrs
}

 示例:定义一个泛型类

class Tree<T> {
  name: T
  age: number
  constructor(name: T, age: number){
    this.name = name;
    this.age = age;
  }
}
let yeTree = new Tree<string>('椰子树', 12);
let yaTree = new Tree<boolean>(true, 10)

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作他的属性或方法,因此需要对泛型进行约束,只允许这个函数传入包含某些属性或方法的变量,这就叫泛型约束;

示例:获取数据的长度(则要求此数据包含.length属性)

// 获取数据的长度
interface Ilength {
  length: number
}
function searchLen<T extends Ilength>(data: T): number {
  console.log(data.length);
  return data.length
}
searchLen('123') // 3
searchLen(123) // 报错 Argument of type 'number' is not assignable to parameter of type 'Ilength'

声明合并

如果定义了两个相同名字的函数、接口或类,那么就可以将其进行合并;

函数的合并

可利用函数重载定义多个函数类型:

function test(data: number): number 
function test(data: string): string

// 就可以合并为
function test(data: number | string) : number | string

接口合并

接口中的属性在合并时会简单的合并到一个接口中去:

要注意合并的属性类型必须是唯一的

interface test {
  data: string
}
interface test {
  num: number
}

let one: test = {
  data: '124',
  num: 12
}

// 会将接口自动合并为
interface test {
  data: string
  num: number
}

类的合并

类的合并与接口的合并规则是一样的哟

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷糊的小小淘

整理不易,赏点动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值