TS(TypeScript)学习,从0开始


介绍总结

  • TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。 TypeScript
  • 是一门静态类型、弱类型的语言。 TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript运行时的特性。
  • TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行JavaScript 的环境中。
  • TypeScript 拥有很多编译选项,类型检查的严格程度由你决定。
  • TypeScript 可以和JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。
  • TypeScript增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。

安装TS

命令:npm install -g typescript
查看版本:tsc -v

类型检查示例

(() => {
  function text(str:String) {
    return '你好' + str
  }
  // text() 报错: 应有 1 个参数,但获得 0 个。
  // text(123) 报错:类型“number”的参数不能赋给类型“String”的参数。
  text('123')
  let a:Number = 10
  console.log(a)
})()

编译

浏览器无法运行ts文件需要编译成js文件在浏览器运行

//在终端执行: tsc 文件名。
例:tsc .\index.ts

自动编译

  1. 在终端执行 tsc --init
    在这里插入图片描述
  2. 运行ts监视任务:运行成功后后续编辑保存代码时他会自动编译
    在这里插入图片描述

类型声明

指定ts变量(参数、形参)的类型 ts编译器会自动检查错误

1、基础类型:

let num:Number = 10
// num = '' 报错
num = 11
// 0b1010 和 0o744 是 ES6 中的二进制和八进制表示法,它们会被编译为十进制数字。
// ES6 中的二进制表示法
let a1: number = 0b1010
// ES6 中的八进制表示法
let a2: number = 0o744

function abc(a: String){
}
// abc(123) 报错
abc('123)

let u:undefined = undefined;
let n:null = null;
// undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给其他类型的变量
let num:number = undefined;
let u:undefined;
let num:number = u;

2、数组:

let arr:[] = []
// 数组中有值的话必须指定类型
let arr:number[] = [1, 2, 3];
// arr = [1, '2', 3] 报错数组中不允许出现其他类型
// 泛型
let arr1: Array<number> = [1, 1, 2, 3, 5]

3、对象:

// object 表示非原始类型,除了number、string、boolean之外的类型
let obj:object = {}
obj = null
obj = []
obj = new String()
obj = String

4、any: 任意值

// any 类型,则允许被赋值为任意类型
let num:any = 10
num = '11'
let arr:any[] = [1,'2',true,null]

void: JavaScript 没有空值(Void),在 TypeScript 中,可以用 void 表示没有任何返回值的函数

function fun1(): void {
    console.log(123)
    // void和js 中的 return undefined 一个效果
}

类型推断

在没有明确的指定类型的时候推测一个类型

// 1、定义变量的时候直接给变量赋值则定义为对应类型
let a = 123
// a = '123 报错
// 2、定义变量的时候没有赋值,则定义为any类型
let g
g = 123
g = ''
g = {}

联合类型

表示取值可以为多种类型中的一种。

let f: boolean|number = true
f = 123 // 再次赋值,走类型推断
// 当ts不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:

对象的类型 —— 接口

它是对行为的抽象,用于对「对象的形状(Shape)」进行描述。可以理解为一种约束

// 定义了一个接口 Person
// 接口一般首字母大写,有的编程语言中会建议接口的名称加上 I 前缀: IPerson 
interface Person {
   readonly id: number; // readonly 定义只读属性
   name: string;
   age: number;
   sex?:string; // ? 代表可选属性(可以有也可以没有)
   [propName: string]: any; // 接口允许有任意的属性和任意属性值
   // *注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
   [propName: string]: string;
   // 一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,可以使用联合类型
   [propName: string]: string|number;
}
// 定义了一个变量 tom,它的类型是 Person。这样就约束了 tom 的形状必须和接口 Person 一致。
let obj: Person = {
   name: 'Tom',
   age: 25// 定义的变量比接口少了一些属性是不允许的:
   // a: '123' 报错:对象字面量只能指定已知属性,并且“a”不在类型“Person”中。
};
// obj.id = 11 报错:无法为“id”赋值,因为它是只读属性

数组类型 —— 接口

// 不常用
interface IArr {
  [index: number]: number; // index表示数组的下标
}
let arr: IArr = [1,2,3]

函数类型

1、接口:

interface Ifun1 {
  // (参数:类型,...):返回值的类型
  (a: String, b: String): boolean
}
let fun1:Ifun1 = function(a: string, b:string): boolean {
  return true
}

2、函数声明:

// js中
// 函数声明, 命名函数
// function a(a,b) {}
// 函数表达式,匿名函数
// let a1 = function(a,b) {}

// ts中
// 函数声明, 命名函数
function a(a: number,b: number): number {
  return a + b
}
let c: number = a(1, 2)
// 函数表达式,匿名函数
let a1:(a: number,b: number)=>number = function(a: number,b: number): number {
  return a + b
}
let c1: number = a1(1, 2)

3、可选参数和默认参数:

// 可选参数和默认参数
let getName = function(x:string, y?:string, z:string='你好'):string {
  return x + y + z
}
// 可选参数:?(必选参数不能位于可选参数的后面)
// 默认参数:string='你好'(可以放在必选参数和可选参数的后面)
getName('zhang')

4、剩余参数和函数重载:

// 剩余参数: 使用ES6中的...来获取剩余参数
function fun(x:string,...arr:number[]) {
  console.log(arr);
}
fun('',1,2,3,4) // log: [1,2,3,4]

// 函数重载: 允许一个函数接受不同数量或类型的参数
// 使用联合类型
// 缺点:不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
// 重载声明: 使用重载定义多个函数类型(精确表达返回值)
function fn(x:string,y:string):string
function fn(x:number,y:number):number
// 函数实现
function fn(x:string|number,y:string|number):string|number {
  if(typeof x == 'string' && typeof y == 'string') {
    return x + y // 字符串拼接
  }else if(typeof x == 'number' && typeof y == 'number') {
    return x + y // 数字相加
  }
}
fn(1,2) // log: 3
fn('zhang','san') // log: zhangsan

类型断言

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法。可以用来手动指定一个值的类型,这样就可以访问某个类型的方法了。
语法:
变量 as 类型:(建议使用)
<类型>值:在 ts 中除了表示类型断言之外,也可能是表示一个泛型。(不建议使用)

例:将联合类型断言为其中一个类型

// 需求: 获取到字符串或者字数的长度
// 类型断言可以手动指定一个属性
function getLength(x:string|number):number {
  if((x as string) .length) {
    return (<string>x).length
  }else {
    return x.toString().length
  }
}
getLength('123') // log: 3
getLength(123) // log: 3

1、将任何一个类型断言为 any:

//  any类型是可以访问任何属性和方法的
(window as any).foo = 1

上面的例子中,我们需要将 window 上添加一个属性 foo,但 TypeScript 编译时会报错,提示我们 window 上不存在 foo 属性。
此时我们可以使用 as any 临时将 window 断言为 any 类型
需要注意:它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any。

2、将 any 断言为一个具体的类型

function abc(x:any, y:any):any {
  return x + y
}
let num = abc(1,2) as number // num => 数值类型
let str = abc('1','2') as string // str => 数值类型

总结:
联合类型可以被断言为其中一个类型
任何类型都可以被断言为 any
any 可以被断言为任何类型

类型别名

类型别名用来给一个类型起个新名字。

// 通过 type 给类型起别名
type s = string
let str1:s = '123'
// 常用于联合类型
type all = string | number | boolean
let a2:all = '123'
let a3:all = 123 

字符串字面量类型

约束取值只能是某几个字符串中的一个。

type strType = '张三' | '李四' | '王二麻子'
let names:strType = '王二麻子'
// let names:strType = '张三123' 报错:不能将类型“"张三123"”分配给类型“strType”。

元组(Tuple)

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

// 普通声明
let arr1:number[] = [1,2,3]

// 元组(Tuple)合并了不同类型的对象。
let Tarr:[string,number] = ['123',123]
// 当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。
// Tarr = ['123'] 报错:源具有 1 个元素,但目标需要 2 个。

// 当添加元素时,它的类型会被限制为元组中每个类型的联合类型。
Tarr.push(456)
Tarr.push('张三')
// Tarr.push(true) 报错:不能赋给类型“string | number”的参数

枚举

用于取值被限定在一定范围内的场景

例:使用枚举给一组数值赋予名称

// 枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射
enum numType {
  one = 1,// 手动赋值,没有手动赋值的话第一个参数默认为0
  two,// 没有手动赋值的枚举成员会自动递增
  three
}
console.log(numType)
/**
 * log结果为:
 * {
    one: 1,
    two: 2,
    three: 3,
    1: one,
    2: two,
    3: three
  }
 */

1、枚举项的两种类型

常数项(constant member)和计算所得项(computed member)。

enum color {
  red, // 常数项
  blue = 'blue'.length, // 计算所得项
  // green 报错: 计算所得项后面不能跟着非手动赋值的枚举成员
}

2、常数枚举

常数枚举是使用 const enum 定义的枚举类型
常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。

const enum obj1 {
  o,
  b,
  // j = '123'.length 报错:常数枚举不能包含计算成员
  j = 1+1 // 可以因为他不属于计算所得项
}
console.log(obj1.o); // log: 0
console.log(obj1.b); // log: 1
console.log(obj1.j); // log: 2

3、外部枚举(Ambient Enums)

使用 declare enum 定义的枚举类型
常用在声明文件中
declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

declare enum obj2 {
  a, b, c
}
// 报错:因为在编译结果中会被删除所以在编译后的js文件中访问不到
// console.log(obj2.a);

// declare 和 const 结合使用也是可以的
declare const enum obj3 {
  a, b
}
// 枚举对象会先初始化成功后再删除
console.log(obj3.a); // log: 0
// console.log(obj2.a);

描述了所创建的对象共同的属性和方法

1、属性和方法

// 实例化对象
class Point {
  // 在ts中需要提前定义好属性和类型
  x: number
  y: number
  constructor(x:number,y:number) {
    this.x = x,
    this.y = y
  }
  toStr() {
    return 'str=>' + (this.x + this.y)
  }
}
let p = new Point(1,2) // new 的时候会执行类中的构造方法constructor
console.log(p); // log: Point { x: 1, y: 2 }
console.log(typeof p.x); // log: number
console.log(p.toStr());; // log: str=>3

2、继承

扩展现有的类,通过继承

使用 extends 关键字实现继承。
子类中使用 super 关键字来调用父类的构造函数和方法。
子类还可以重写父类的方法

class A { // 父类
  name:string
  age:number
  constructor(name:string,age:number) {
    this.name = name
    this.age = age
  }
  Hi() {
    return this.name + '|' + this.age
  }
}
let A1 = new A('李雷',18)
console.log(A1.Hi()); // log: 李雷|18

// b extends(继承) A
class b extends A { // 子类
  constructor(name:string,age:number) {
    // 使用 super 调用父类的构造函数(将父类的参数构建到子类)
    super(name,age)
  }
  // 可以调用父类的方法, 也可以重写父类的方法
  Hi() {
    // 可以通过 super 调用父类中的方法
    return  '我是b类 =>' + super.Hi()
  }
}
let b1 = new b('韩梅梅',10)
console.log(b1.Hi()); // log: 我是b类 =>韩梅梅|10

3、存取器

使用 gettersetter 控制对象成员的赋值和读取行为

class Name {
  cnName: string
  enName: string
  constructor(cnName: string,enName: string) {
    this.cnName = cnName
    this.enName = enName
  }
  // 读取器: 用来读取数据
  get nameAll() {
    return this.cnName + '|' + this.enName
  }
  // 设置器:用来设置数据
  set nameAll(val:string) {
    let ns = val.split('-')
    this.cnName = ns[0]
    this.enName = ns[1]
    return 
  }
}
let N = new Name('张三','ZS')
console.log(N); // log: Name { cnName: '张三', enName: 'ZS' }
// 获取读取器生成的值
console.log(N.nameAll); // log: 张三|ZS
// 通过设置器改变值
N.nameAll = '李四-LS'
console.log(N); // log: Name { cnName: '李四', enName: 'LS' }
// 再次通过读取器获取改变后的值
console.log(N.nameAll); // log: 李四|LS

4、修饰符

public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public
private 修饰的属性或方法是私有的,不能在声明它的类的外部访问,并且子类可以继承但是不能被访问
protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
可以直接定义在构造函数参数上,ts会自动创建和初始化参数

class B {
  public name1: string // 公有属性(默认)
  private age: number // 私有属性
  protected type: boolean // 受保护的
  constructor(name1:string,age:number,type:boolean) { // 公有
    this.name1 = name1
    this.age = age
    this.type = type
  }
  Hi() { // 公有
    return console.log('我是 B');
  }
}
let b2 = new B('张三',12,true)
console.log(b2);
// console.log(b2.age); 报错:私有属性无法在外部被访问
// console.log(b2.type); 报错:受保护属性无法在外部访问,只能在父类和子类中访问
class C extends B {
  constructor(name1:string,age:number,type:boolean) {
    super(name1,age,type)
  }
  play() {
    // console.log(this.age); 报错:私有属性在子类可以继承不能访问
    console.log(this.type); // 受保护的属性在子类中可以访问
  }
}
let c2 = new C('李四',10,false)
// 所有类型的属性、方法都可以继承
console.log(c2); // log:{ name1: '李四', age: 10, type: false }

5、readonly 修饰符

readonly 只读属性,但是在构造函数是可以修改的
与其他修饰符共用的时候只能跟在其他修饰符的后面
可以直接定义在构造函数参数上,ts会自动创建和初始化参数

class X {
  readonly name:string
  constructor(name:string,public readonly age:number) {
    this.name = name
    this.age = age
  }
  Hi() {
    // this.age = 15 报错:只读属性,不能修改
  }
}
let x = new X('name',12)
// X.name = 'hh' 报错:只读不能修改

6、抽象类

abstract 用于定义抽象类和其中的抽象方法。
抽象类是不允许被实例化的,是为子类服务的
抽象类中的抽象方法只能且必须被子类实现

abstract class Y {
  name: string
  abstract age:number // 抽象属性
  constructor(name:string) {
    this.name = name
    // this.age = age 报错:不能在构造函数中访问抽象属性
  }
  abstract Hi() // 抽象方法在抽象类中不能有具体实现
}
// let y = new Y() 报错: 抽象类不能被实例化
class Z extends Y {
  age:number // 子类继承抽象类需要定义属性
  constructor(name:string,age:number) {
    super(name)
    // super(age) 报错:抽象属性未使用构造函数构造不能使用super调用
    this.age = age
  }
  Hi() { // 子类必须实现抽象类中的方法
    console.log(this.name+this.age);
  }
}
let z = new Z('张三',18)
z.Hi()

7、类的类型

当子类继承父类的属性完全相同时可以互相定义类型

class Car {
  name:string
  constructor(name:string) {
    this.name = name
  }
}
class BYD extends Car {
  constructor(name) {
    super(name)
  }
}
class Ben extends Car {
  age:number
  constructor(name) {
    super(name)
  }
}
// 当子类继承父类的属性完全相同时可以互相定义类型
let car:BYD = new Car('')
let byd:Car = new BYD('')
// let car:Ben = new Car('') 报错:类型 "Car" 中缺少属性 "age",但类型 "Ben" 中需要该属性

类与接口

1、类实现接口

提取不同类中共有的特性提取成接口
使用 implements(实现)接口,用来约束类中必须要有接口中定义的方法
一个类可以实现多个接口

interface Ising { // 唱歌 
  sing() // 接口里的方法不需要实现
}
interface Icar { // 车
  car()
}
// 一个类实现多个接口
class Per implements Ising,Icar { // 人
  // 接口中定义的方法需要在具体使用的类中实现
  sing() {
    console.log('人-唱歌');
  }
  car() {
    console.log('人-车');
    
  }
}
class An implements Ising { // 动物
  sing() {
    console.log('动物-唱歌');
  }
}
let per = new Per()
per.sing()
let an = new An()
an.sing()

2、接口继承接口

一个接口可以去继承多个接口

interface A1 {
  Afun()
}
interface B1 {
  Bfun()
}
// 接口可以去继承多个接口
interface AB extends A1,B1 {
  // 可以写自己的也可以不写
}
class I implements AB {
  Afun() {

  }
  Bfun() {

  }
}

3、接口继承类

接口可以继承类中的实例属性和实例方法

class NewPer {
  name:string
  constructor(name:string) {
    this.name = name    
  }
  Hi() {
    console.log('Hai');
  }
}
// 接口可以继承类中的实例属性和实例方法
interface IPer extends NewPer {
  age:number
}
let per1:IPer = {
  name: '',
  age: 12,
  Hi() {

  }
}
console.log(per1.Hi());

声明合并

1、接口合并

接口中的属性在合并时会简单的合并到一个接口中
合并的属性的类型和值必须相同

interface cat {
  name: '小黑'
  // age: 10 报错:与其他接口定义的值不同
}
interface cat {
  name: '小黑',
  age: 5
}
const ICat:cat = {
  name: '小黑',
  age: 5
}

2、函数的合并 => 和函数重载相同

3、类的合并 => 和接口合并相同

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

1、使用泛型

<T> 其中 T 用来指代任意输入的类型

// 例:定义一个函数传入两个参数。第一个是数据,第二个是数量
// 需求:根据数量生成对应个数的数据,放在一个数组中
// T 表示输入任意类型
function getArr<T>(val:T,num:number):T[] {
  const arr:T[] = []
  for (let i = 0; i < num; i++) {
    arr.push(val)
  }
  return arr
}
// 使用泛型时不需要先确定类型,可以在使用的时候确定
// 如果使用时未指定类型,走类型推断
console.log(getArr(123,3));
console.log(getArr<string>('123',3));

2、多个泛型的使用

定义泛型的时候,可以一次定义多个类型参数

// 需求:['123',123] ==> [123,'123']
function updateArr<T,U>(t:[T,U]):[U,T] {
  return [t[1],t[0]]
}
console.log(updateArr(['123',123]));

3、泛型约束

在函数内使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法

interface ILength { // 约束参数必须有 length 属性
  length:number
}
function getLength<T extends ILength>(x:T): number {
  return x.length
}
console.log(getLength('123'));
// getLength(true) 报错

4、泛型接口和泛型类

提前把泛型参数定义到接口名上。此时在使用泛型接口的时候,需要定义泛型的类型

// 第一种写法
// interface IArr1 {
//   <T>(val:T,num:number):Array<T>
// }
// 第二种写法: 提前把泛型参数定义到接口名上。此时在使用泛型接口的时候,需要定义泛型的类型
interface IArr1<T> {
  (val:T,num:number):T[]
}
// 第一种写法
// let getArr1:IArr1 = function <T>(val:T,num:number):T[] {
// 第二种:需要定义泛型的类型
let getArr1:IArr1<string> = function <T>(val:T,num:number):T[] {
  const arr:T[] = []
  for (let i = 0; i < num; i++) {
    arr.push(val)
  }
  return arr
}
console.log(getArr1('123',3));

// 泛型类
class Person1<T> {
  name:String
  age:T
  constructor(name:string,age:T) {
    this.name = name
    this.age = age
  }
}
let person = new Person1<string>("张三",'18')
let person1 = new Person1<number>('张三',18)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值