typescript语法

基础语法

基础类型和对象类型

TypeScript中基础类型主要包括:number、string、null、undefined、boolean、symbol、void,对象类型包括了{}、[]、class、function等。

//基础类型:number、string、null、undefined、boolean、symbol、void
const count: number = 123;
const str: string = 'lx';

//对象类型:
// 对象
const person: {
  name: string,
  age: number
} = {
  name: 'lx',
  age: 18
}
//数组
const numbers: number[] = [1, 2, 3];
// 类
class Person {}
const lx: Person = new Person();
//函数
const getTotal: () => number = () => {
  return 123;
}

定义的变量有可能有多种类型如何书写?

const a: number | string = 123;
const arr: (number | string)[] = [1, 'b', 2];

类型注解和类型推断

1、类型注解(type annotation):我们来告诉TS变量是什么类型
2、类型推断(type interface): TS会自动去尝试分析变量的类型
如果TS能自动分析变量类型,我们就不需要写类型注解;如果TS无法分析变量类型,我们就需要雪上类型注解

let countInterface = 1;
const first = 1;
const second = 2;
//如果定义变量写为两行,需要使用类型注解
let count: number;
count = 123;

在这里插入图片描述
鼠标移入到变量名上TS会自动分析出变量类型,此时我们就不用去写类型注解

//getTotal中参数需要类型注解,因为不知道调用该方法会传入什么类型的参数
function getTotal(first: number, second: number) {
  return first + second
}
//TotalCount为getTotal方法的返回值,现在确定为number类型,所以不用类型注解
const totalCount = getTotal(1, 2)

函数相关类型

1、确定函数返回类型

//函数返回类型为number
function add(x: number, y: number): number {
  return x + y
}
// 函数没有返回值
function sayHello(): void {
  console.log('hello');
}
// 函数不会执行到最后一行
function errorEmitter() : never {
  throw new Error()
  console.log(123)
}

2、函数形参解构赋值

// 即使只有一个参数也要用对象的方式
function add({fir, sec}: {fir: number, sec: number}): number {
  return fir + sec
}
const total = add({fir: 1, sec: 2});

3、多种定义函数的方式

function fun1(str: string): number {
  return parseInt(str, 10)
}
const fun2 = (str: string): number => {
  return parseInt(str, 10)
}
// 一般返回类型能够确定就不需要类型注解
const fun2 = (str: string) => {
  return parseInt(str, 10)
}
// 下面的类型注解不能省略,因为语法会错误
const fun3: (str: string) => number = (str) => {
  return parseInt(str, 10)
}

数组与元组

1、数组

const arr: (number | string)[] = [1, 'b', 2];
const strArr: string[] = ['1', 'b', '2'];
const nullArr: null[] = [null, undefined];

//类型别名 type alias
type Person = {
  name: string;
  age: number;
}
class Student {
  name: string;
  age: number
}
// 可以直接new一个实例,只要内部类型一样就可
const objectArr: Person[] = [
  new Student(),
  {
    name: 'lx',
    age: 18
  }
]

2、元组(tuple):类型和长度都要对应

const studentArr: [string, string, number] = ['lx', 'male', 18];
const studentList: [string, string, number][] = [
  ['lx', 'male', 18],
  ['lx1', 'male', 19],
  ['lx2', 'female', 20]
]

Interface接口

1、官方定义:接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。
实际上,就是把一些共有的属性和方法等提取出来。看下面例子:

const getPersonName = (person: {name: string}): string => {
  return person.name
}
const serPersonName = (person: {name: string}, name: string) => {
  person.name = name
}

getPersonName 和serPersonName 方法中参数person都用到了{name: string}这样的格式校验,所以,现在可以把共有的{name: string}提取出来定义一个接口。

interface Person {
  //readonly name: string; 表示name只读
  name: string;
  age?: number; // ?:表示age可有可无
  [propName: string]: any;//表示接口内可以有其他的属性,属性的名字是string类型,值可为任意类型
  say(): string;// 属性可以有一个名为say的方法,该方法返回值为string类型
}
const getPersonName = (person: Person): string => {
  return person.name
}
const setPersonName = (person: Person, name: string) => {
  person.name = name
}
const person = {
  name: 'lx',
  sex: 'male',
  say: () => {return 'say hello'}
}
getPersonName({
  name: 'lx',
  sex: 'male',
  say: () => {
    return ''
  }
})// 当Person接口中没有sex的定义时,这么写会报错,此时typescript会对其进行强校验
//接口内有[propName: string]: any也不会报错
getPersonName(person)// 当Person接口中没有sex的定义时,缓存一下变量再传进去不会报错
setPersonName(person, 'zr')

2、一个类如果想应用一个接口,可以使用implements

//User类中必须要有name值和say方法;这个类必须要存在接口内存在的方法和属性
class User implements Person {
  name = 'lx';
  say() {
    return 'hello'
  }
}

3、接口的继承:

//Teacher 接口继承了Person接口内的所有属性和方法
interface Teacher extends Person {
  teach() : string
}
const serPersonName = (person: Teacher, name: string) => {
  person.name = name
}
const person = {
  name: 'lx',
  sex: 'male',
  say: () => {return 'say hello'},
  teach: () => {
    return 'teach'
  } // 此时person内必须要有teach方法
}

4、定义函数类型声明

//interface可以定义一个函数类型
interface sayHi {
  (word: string): string//函数必须接收一个word的参数,为string类型,返回值类型也是string类型
}
const say: sayHi = (word: string) => {
  return word
}

类和类的继承

1、TypeScript 是面向对象的 JavaScript。类描述了创建的对象共同的属性和方法。

class Person {
  name = 'lx';
  getName() {
    return this.name
  }
}
const person = new Person();
console.log(person.getName())

2、类的继承和重写

class Person {
  name = 'lx';
  getName() {
    return this.name
  }
}
class Teacher extends Person {
  getTeacherName() {
    return 'teach'
  }
  // 重写Person类中的getName方法
  getName() {
    return 'msld' + super.getName() // super代表父类
    // super使用场景:子类把父类的方法重写了,但是我还想调用父类的该方法就用super
  }
}
const person = new Person();
const teacher = new Teacher()
console.log(teacher.getName()) //maldlx
console.log(teacher.getTeacherName()) // teach

类中的访问类型和构造器

1、访问类型有三种:public、private。protected
public:类中属性和方法默认是public类型,允许在类的内外被调用
private:只允许在类内被调用
protected:只允许在类内及继承的子类中调用

class Person {
  name: string;
  private age: number;
  protected say() {
  	this.name;
  	this.age;
    return 'hello';
  }
}
class Teacher extends Person {
  sayHi() {
    this.age; // 报错
    this.say()
  }
}
const person = new Person()
console.log(person.name)
console.log(person.age)// 报错
console.log(person.say())// 报错

2、构造器constructor
每一个类中都有一个constructor方法,在被实例化时会自动执行该方法。

class Person {
  //传统写法
  // name: string;
  // constructor(name:string) {
  //   this.name = name
  // }

  //简化写法
  constructor(public name: string){}
}
const person = new Person('lx')
console.log(person.name) //lx

如果父类有构造器,子类也要声明构造器,就必须要在子类构造器中调用父类构造器,使用super()代表调用父类的构造器。且还要按照父类构造器的要求把参数传过去。如果父类中没有写constructor,子类中的constructor方法也要调用空的super()

class Person {
  constructor(public name: string){}
}

class Teacher extends Person {
  constructor(public age: number) {
    super('lx');
  }
}
const teacher = new Teacher(18)
console.log(teacher.name) //lx
console.log(teacher.age) //18

静态属性、setter和getter

1、静态属性(static):挂载在类上,不是挂载在类的实例上。静态方法内只能访问静态成员,因为静态的内容是随着类的加载而加载的,它是先进入内存的。如果静态方法调用成员变量时,类没有被实例化时则会报错,所以限制静态方法只能使用静态成员
使用static实现一个简单的单例模式:

//单例模式
class Demo {
  private static instance: Demo; // 存储new出来的Demo
  private constructor(public name: string){}
  //static 私有属性,挂载在类上,可直接通过类名调用
  static getInstance() {
    if(!this.instance) {
      this.instance = new Demo('lx');
    }
    return this.instance;
  }
}
const demo1 = Demo.getInstance();
const demo2 = Demo.getInstance(); // 此时demo1和demo2是一模一样的实例
console.log(demo1.name) // lx
console.log(demo2.name) // lx

2、setter和getter

class Person {
  //使用private保护私有属性,不让其在类的外部被调用,一般在其前面加下划线表示私有属性
  constructor(private _name: string) {}
  // 如果想获取私有属性,可以使用get
  //现在在外部调用name其实是调用了实际的name值+msld,起到保密作用
  get name() { 
    return this._name + 'msld' // 对name做一些处理后暴露在外部使用
  }
  // 对设置的name值做了一次处理,也相当于保护了name值
  set name(name: string) {
    const realName = name.split(' ')[0]
    this._name = realName
  }
}

const person = new Person('lx');
console.log(person.name) //lxmsld
person.name = 'lx msld'
console.log(person.name)//lxmsld

抽象类

1、抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
2、抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
3、父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
4、抽象类表示的是一种继承关系,一个类只能继承一个抽象类

//抽象类 里面的属性和方法是它的子类中的一些共有的属性和方法
// 抽象类自己也可以定义自己的属性和方法
abstract class Area{
  getName() {
    return 'lx'
  }
  // 定义抽象方法,在子类继承父类时要在子类中写好该抽象方法
  abstract getArea(): number 
}
class Circle extends Area{
  getArea() {
    return 1
  }
}
class Square extends Area{
  getArea() {
    return 2
  }
}
const circle = new Circle()
console.log(circle.getName())// lx
console.log(circle.getArea())// 1
const square = new Square()
console.log(square.getName())// lx
console.log(square.getArea())// 2

进阶语法

TypeScript中的配置文件

1、tsc --init初始化一个ts项目,会生成一个tsConfig.ts文件(编译配置文件)。如果tsc使用不了,要全局安装TypeScript。
2、使用tsc demo.ts可以编译该文件,这种方式的编译不会执行tsConfig.ts。直接使用tsc编译会编译全部ts文件且执行config文件,但若我只想编译部分ts文件,可以在配置文件中配置 includeexclude或者 files配置项

{
  "include": ["demo.ts"],
  "exclude": ["demo1.ts"],
  "files": ["demo2.ts"],
  "compilerOptions": {
  ...
  }
}

3、compilerOptions中有很多配置项,例举几个:
removeComments: 编译时去除ts文件中的注释
strictNullChecks:不强制进行null校验
rootDir:允许编译的文件路径
outDir:编译后文件存放的路径
incremental:上次编译过的文件就不会再编译,只编译修改过和未编译过的文件
noUnusedLocals:对局部定义未使用的变量做警告

更多配置可参考TypeScript官网

联合类型和类型保护

联合类型(Union Types)可以通过(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值
只能赋值指定的类型,如果赋值其它类型就会报错。
类型保护:我们可以通过 typeofinstanceofin字面量类型将代码分割成范围更小的代码块,在这一小块中,变量的类型是确定的。

interface Bird{
  fly: boolean;
  sing: () => {}
}
interface Dog{
  fly: boolean;
  bark: () => {}
}
// 联合类型  Bird | Dog 这种方式
function trainAnimal(animal: Bird | Dog) {
  if(animal.fly) {
    (animal as Bird).sing(); // 断言方式的类型保护
  } else {
    (animal as Dog).bark();
  }
}

function trainAnimalSec(animal: Bird | Dog) {
  if('sing' in animal) {// in语法的类型保护
    animal.sing(); 
  } else {
    animal.bark();
  }
}

function add(first: string | number, second: string | number) {
  if(typeof first === 'string' || typeof second === 'string') { // typeof语法的类型保护
    return `${first}${second}`
  } else {
    return first + second
  }
}

class NumberObj {
  count: number;
  constructor(){
    this.count = 1
  }
}
function addSec(first: object | NumberObj, second: object | NumberObj) {
	// instanceof语法的类型保护
  if(first instanceof NumberObj && second instanceof NumberObj) { 
    return first.count + second.count
  }
  return 0
}

Enum枚举类型

枚举使用 enum关键字来定义
枚举成员会被赋值为从 0 开始递增的数字,可手动赋值,同时也会对枚举值到枚举名进行反向映射:

enum Status {
  OFFLINE,
  ONLINE = 4,
  DELETE
}

console.log(Status.OFFLINE) // 0
console.log(Status.ONLINE) // 4
console.log(Status.DELETE) // 5
console.log(Status[0]) // OFFLINE

手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的),只可在最后一项赋值。

enum Status {
  OFFLINE,
  ONLINE,
  DELETE = <any>'a'
}
console.log(Status.DELETE) // a

泛型

泛型(generic):泛指的类型,用<>包裹
例举某一场景:定义一个方法,传两个参数,此时我想让两个参数的类型必须得一样,但又不确定都是什么类型时可使用泛型

函数中的泛型

//*********函数中的泛型**********
// add方法内两个参数类型都为T,T是不确定的类型,方法调用时才会确定
function Add<T>(first: T, second: T) {
  return `${first}${second}`
}
Add<string>('1', '2');
// 泛型内也可以使用多个类型
function Add1<T, P>(first: T, second: P) {
  return `${first}${second}`
}
Add1<string, number>('1', 2);
// 方法返回类型也可以使用泛型
function Add2<T, P>(first: T, second: P): T {
  return first
}
Add2<number, string>(1, '2');
// Add2<number, string>(1, '2')可以去掉类型写为Add2(1, '2')
// 前者是显示确定类型,后者根据类型推断隐式确定类型

方法的参数类型还可以为一个泛型数组

// ABC[]  和  Array<ABC>等价
function map<ABC>(param: Array<ABC>) {
  return param
}
map<string>(['123', 'hello'])

类中的泛型

class DataManage<T>{
  constructor(private data: T[]){}
  getItem(index: number): T {
    return this.data[index];
  }
}
const data = new DataManage([1, 2, 3])
console.log(data.getItem(2)) // 3

上面例子中,我想在getItem方法中返回this.data[index].name,因为在构造函数中定义的data是一个泛型数组,现在并不能确定this.data[index]是否有name属性,所以我们可以定义一个接口,然后让T继承该接口。此时我们传的数组参数中的每一项必须要满足有name属性

interface Item{
  name: string;
}

class DataManage<T extends Item>{
  constructor(private data: T[]){}
  getItem(index: number): string {
    return this.data[index].name;
  }
}
const data = new DataManage([
{
  name: 'luo'
},
{
  name: 'x'
}])
console.log(data.getItem(0)) // luo
console.log(data.getItem(1)) // x

现在如果我想T的类型只能是string或number类型,不允许是其他类型如何处理?
可使用extends字段

class DataManage<T extends string | number>{
  constructor(private data: T[]){}
  getItem(index: number): T{
    return this.data[index];
  }
}
const data = new DataManage<string>(['123'])
console.log(data.getItem(0)) // 123

泛型中keyof的用法

// 泛型中的keyof
interface Person {
  name: string,
  age: number,
  gender: string
}
class Info {
  constructor(private info: Person) {}
  getInfo<T extends keyof Person>(key: T): Person[T] {
    return this.info[key]
  }
}

const info = new Info({
  name: 'luo',
  age: 18,
  gender: 'man'
})
const i = info.getInfo('gender')
console.log(i) //man

keyof Person是对Person这个接口的每一个值做遍历
第一次遍历:
T extends ‘name’ =====> type T = ‘name’
key: ‘name’
返回值类型:Person[‘name’] = string

高级语法

类的装饰器

简单的说,类的装饰器就是对类修饰的一个工具。
1、其本身是一个函数,通过@符号来使用
2、如果想使用装饰器,需要在tsconfig.josn配置文件中将experimentalDecorators设置为true
3、装饰器是对类做修饰,而不是对实例做修饰,所以装饰器是在类定义好之后就立即执行。
4、类装饰器接收的参数是构造函数,在装饰器内可以对构造函数进行修改。

function decorator(constructor: any) {
  //创建一个getName方法
  constructor.prototype.getName = () => {
    console.log('luo')
  }
}

@decorator
class Test{}

const test = new Test();
(test as any).getName() // luo

可以使用工厂模式来判断是否对这个类做修饰(该方法容易理解但是不太正规)

function decorator(flag: boolean) {
  if(flag) {
    return function(constructor: any) {
      constructor.prototype.getName = () => {
        console.log('luo')
      }
    }
  } else {
    return function(constructor: any) {}
  }
}

@decorator(true) // 通过传参来判断是否需要修饰Test类
class Test{}

const test = new Test();
(test as any).getName() // luo

通过泛型来写正规的装饰器:
对装饰器中的泛型做一些解释:
(...args: any[]) => any是一个函数,返回值类型是any,参数是一个数组,数组里面值的类型也是any
new (...args: any[]) => any说明该函数是一个构造函数,T extends new (...args: any[]) => any表示T是一个类或者说是包含了一个构造函数的东西

function decorator<T extends new (...args: any[]) => any>(constructor: T) {
  return class extends constructor{
    name = 'msld'; // 无论test实例如何去设置name值,name始终为msld
    getName() {
      return this.name
    }
  }
}
@decorator
class Test{
  name: string;
  constructor(name: string) {
    this.name = name
  }
}
const test = new Test('luo');
console.log(test); // class_1 { name: 'msld' }
(test as any).getName() // 直接使用test.getName()会报错

在上面所有的例子中,当调用getName方法时我们都不是直接使用test.getName()去调用。而是用了(test as any).getName()这样的方式做了处理。因为在Test类中本身没有getName方法,装饰器中新加的方法在实例中是找不到的。那么我们如何处理能让test直接调用getName方法呢?看下面代码:

//同样使用工厂模式 定义一个函数去返回装饰器
function decorator() {
  return function <T extends new (...args: any[]) => any>(constructor: T) {
    return class extends constructor{
      name = 'msld';
      getName() {
        console.log(this.name)
        return this.name
      }
    }
  }
}

// 执行decorator方法  得到一个装饰器 然后再把类当作参数传进去  最后赋值给Test 
// 此时Test上就会有装饰器中新加的getName方法了
const Test = decorator()(class {
  name: string;
  constructor(name: string) {
    this.name = name
  }
})
const test = new Test('luo');
console.log(test); // class_1 { name: 'msld' }
test.getName() // msld'

方法装饰器

官方定义:方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义。
简单来说,就是可以对方法做一些修饰。
方法装饰器也是一个函数,接收三个参数:
(1)target:当修饰普通方法时,target对应的是类的prototype;当修饰静态方法时,target对应的是类的构造函数
(2)propertyKey:方法的名字
(3)descriptor:方法的属性描述符

function getNameDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log(target, propertyKey, descriptor)  // { getName: [Function (anonymous)] } getName {
                                                //   value: [Function (anonymous)],
                                                //   writable: true,
                                                //   enumerable: true,
                                                //   configurable: true
                                                // }
  descriptor.writable = false
  descriptor.value = function() {
    return 'decorator';
  }
}

class Test{
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  @getNameDecorator
  getName() {
    return this.name;
  }
}

let test = new Test('luo');
// 配置了descriptor.writable = false  就不可对该方法进行修改
// test.getName = () => {
//   return '12345'
// }
// Cannot assign to read only property 'getName' of object '#<Test>'

console.log(test.getName()) // decorator    通过descriptor.value对方法进行了重写

访问器装饰器

访问器装饰器类似于方法装饰器,也为一个函数,且接收的参数和方法装饰器一样。
访问器装饰器声明在一个访问器(get或set)的声明之前。

function visitDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log(target, propertyKey, descriptor)  
  // { getName: [Function (anonymous)], name: [Getter/Setter] } name {
  //   get: [Function: get],
  //   set: [Function: set],
  //   enumerable: true,
  //   configurable: true
  // }
  descriptor.writable = false
}

class Test{
  _name: string;
  constructor(name: string) {
    this._name = name;
  }
  getName() {
    return this._name;
  }
  @visitDecorator
  get name() {
    return this._name
  }
  set name(name: string) {
    this._name = name
  }
}

let test = new Test('lx');
// 配置了descriptor.writable = false 不能修改name
test.name = 'lll'
//  Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
console.log(test.name)

注意:TypeScript不允许同时装饰一个成员的getset访问器,因为在装饰器应用于一个属性描述符(descriptor)时,它联合了get和set访问器,而不是分开声明的。
在这里插入图片描述

属性装饰器

属性装饰器同样是一个函数,使用@符号放在定义的属性之前,与上面的装饰器相比,有以下几点区别:
(1)函数参数只有两个:targetpropertyKey,不接受descriptor
(2)如果想设置descriptor,需要在函数内部定义一个descriptor来实现

function nameDecorator(target: any, propertyKey: string): any {
  const descriptor: PropertyDescriptor = {
    writable: false
  }
  return descriptor
}
class Test{
  @nameDecorator
  name = 'luo';
}
let test = new Test();
test.name = 'msld' //TypeError: Cannot assign to read only property 'name' of object '#<Test>'

(3)不能在函数内直接修改属性的值,因为修改的是原型上的属性值,而不是实例上的属性值

function nameDecorator(target: any, propertyKey: string): any {
  target[propertyKey] = 'msld' // 这里修改的并不是实例上的name,而是原型上的name
}
class Test{
  @nameDecorator
  name = 'luo';
}
let test = new Test();
console.log(test.name) // luo   这里不会输出装饰器中设置的name值msld
console.log((test as any).__proto__.name) // msld

参数装饰器

参数装饰器同样是一个函数,使用@符号放在定义的方法参数之前。
参数装饰器接收三个参数:targetpropertyKey(不是参数名,而是方法名)、parameterIndex(第几个参数)。
注意:参数装饰器只能用来监视一个方法的参数是否被传入。

function paramDecorator(target: any, propertyKey: string, paramIndex: number){
  console.log(target, propertyKey, paramIndex) //{ getInfo: [Function (anonymous)] } getInfo 0
}
class Test{
  getInfo(@paramDecorator name: string, age: number) {
    console.log(name, age)
  }
}
let test = new Test();
test.getInfo('lx', 18) //lx 18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值