TypeScript — 基本语法总结

266 阅读16分钟
前言

本人是一个刚入行的菜鸡前端程序员,写这个文章的目的只是为了记录自己学习的笔记与成果,如有不足和错误之处还请大家多多指点。

TypeScript 介绍
  • TypeScript 是由微软开发的一款开源的编程语言。
  • TypeScript 是 JavaScript 的超级,遵循最新的 ES6/ES5 规范。TypeScript 扩展了 JavaScript 的语法。
  • TypeScript 更像后端 Java、C# 这样的面向对象语言可以让 js 开发大型企业项目。
  • 谷歌也在大力支持 TypeScript 的推广,谷歌的 angular2.x+ 就是基于 TypeScript 语法。
  • 最新的 vue 、 react 也可以集成 TypeScript
TypeScript 安装 编译
  • 安装
npm install -g typescript
  • 编译
tsc helloworld.ts
TypeScript 开发工具 VsCode 自动编译 .ts 文件
  • 创建 tsconfig.json 文件 tsc --init 生成配置文件
tsc --init
  • 修改 tsconfig.json 里面的 "outDir" 配置
  • 点击菜单 任务 -> 运行任务 点击 tsc:监视-tsconfig.json 然后就可以自动生成代码
TypeScript 中的数据类型

TypeScript 中为了使编写代码更规范,更利于维护,增加了类型校验,在TypeScript中主要给我们提供了以下数据类型。
TypeScript 中定义变量必须指定类型

布尔类型 (Boolean)
var flag:boolean = true
数字类型 (Number)
var num:number = 123
字符串类型 (String)
var str:string = 'abc'
数组类型 (Array)
//  第一种
var arr:number[] = [1,2,4] //定义一个数组,指定数组中所有的值都是number类型

//  第二种 
var arr:Array<number> = [1,3,4] //定义一个数组,指定数组中所有的值都是number类型

// 第三种 
var arr:any[] = [1, 'aa', 4] //定义一个数组,数组中的所有值可以是任意类型


元组类型 (Tuple) - 属于数组的一种
//可以给数组中的每一个位置指定类型
var arr:[number,string] = [1, 'this is ts']
枚举类型 (Enum)

随着计算机的不断普及,程序不仅只用于数值计算,还更广泛的用于处理非数值的数据。例如: 性别、月份、星期几、颜色、单位名、学历、职业等,都不是数值类型。在其它的程序设计语言中,一般用一个数值来代表某一状态,则程序就很统一阅读和理解。也就是说,事先考虑到某一变量可能取得值,尽量用自然语言中含义清楚得单词来表示它得每一个值,这种方法称为枚举方法,用这种方法定义得类型称为枚举类型。

/*
enum 枚举名 {
  标识符[=整型常数]
  标识符[=整型常数]
  ...
  标识符[=整型常数]
}
*/
enum flag {
  success=1,error=0
}
let s:flag=flag.success
console.log(s) // 1

enum Color {
  blue,red,'orange'
}
let c:Color=Color.red
console.log(c) // 1 如果标识符没有赋值,那么它得值就是下标
任意类型 (Any)
var num:any = 123  // num可以是任意类型  

Null / Undefined - 其它类型的子类型
var num:number;
console.log(num) //输出:undefined  ts报错

var num:undefined;
console.log(num)  //输出:undefined 正确 

// 定义没有赋值就是 undefined
var num:number | undefined  // num可以为number类型或 undefined
Void 类型

TypeScript 中的 void 表示没有任何类型,一般用于定义方法的时候方法没有返回值

//表示方法没有返回任何类型
function run():void {
  console.log('run)
}
Never 类型

never类型 - 其他类型 (包括 null、undefined) 的子类型,代表从不会出现的值。这意味着声明never的变量只能被never类型所赋值。

var a:never;

a = 123 //报错 

a=(() => {
  throw new Error('错误')
})()
TypeScript中的函数
TypeScript 函数的定义
  • 函数声明
// 指定函数返回一个 string
function run():string {
  return 'aaa'   
}
// 没有返回值的方法 
function run():void {
  console.log('run')
}
  • 匿名函数
var  fun2 = function():number {
  return 124
}
  • ts中定义方法传参
/* 参数name为string,age为number。返回一个string */
function getInfo(name:string,age:number):string {
  return `${name} ---- ${age}`
}
  • 方法的可选参数
// age 可传可不传 
function getInfo(name:string,age?:number):string {
  if(age) {
    return `${name} ---- ${age}`
  } else {
    return `${name}`
  }
}
// 注意:可选参数必须配置到参数的最后面
  • 默认参数 es5 中不能设置默认参数, es6和ts中都可以设置默认参数
// name的默认值是 aaa
function getInfo(name:string='aaa',age?:number):string {
 if(age) {
   return `${name} ---- ${age}`
 } else {
   return `${name}`
 }
}
  • 剩余参数 - 三点运算符
function sum(...result:number[]):number {
  let sum = 0
  for(let i = 0; i < result.length; i++) {
    sum += result[i]
  }
  return sum
}
  • 函数重载 java 中方法的重载:重载指的是两个或两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。

TypeScript 中重载:通过为同一个函数提供多个函数类型定义来试下多种功能的目的。

 function getInfo(name:string):string;
 function getInfo(age:number):string;
 function getInfo(str:any):any {
   if(typeof str === 'string') {
     return `我叫${str}`
   } else {
     return `我的年龄是${str}`
   }
 }
TypeScript 中的类
class Person {
  name:string; //属性 前面省略了public 关键词 
  constructor (name:string) { //构造函数 - 实例化类的时候触发的方法
   this.name = name
  }

  run():void {
    console.log(name)
  }
}
  • TypeScript 中实现继承 通过 extends/super 关键字
class Web extends Person {
  constructor(name:string) {
    super(name)
  }
}

当父类和子类有同样的方法时,优先调用子类中的方法

  • 类里面的修饰符 TypeScript里面定义属性的时候给我们提供了三种修饰符
    • public : 公有 在类里面、子类、类外面都可以访问
    • protected : 保护类型 类、子类里面可以访问,类外部没法访问
    • private : 私有 类里面可以访问,子类和类外部都不能访问 属性如果不加修饰符,默认是 public
  • TypeScript 中的静态属性 静态方法
class Person {
   public name:string; //属性 前面省略了public 关键词 
   // static 关键字设置静态属性
   static age = 21
   constructor (name:string) { //构造函数 - 实例化类的时候触发的方法
    this.name = name
   }

   run():void { //实例方法  需要实例化才能调用的方法  
     console.log(name)
   }
  // static 关键字声明静态方法
   static print():void { // 静态方法
    // 静态方法中没办法直接调用类里面的属性
     console.log(Person.age)
   }
 }
  • TypeScript 多态
    多态 - 父类定义一个方法不去实现,让继承它的子类取实现,每一个子类有不同的表现。多态属于继承
    class Animal{
      name:string;
      constructor(name:string) {
        this.name = name
      }
      eat() {
        console.log('eat')
      }
    }
    
    class Dog extends Animal {
      constructor(name:string) {
        super(name)
      }
      eat() {
        return this.name + '吃肉'
      }
    }
    
    class Cat extends Animal {
      constructor(name:string) {
        super(name)
      }
      eat() {
        return this.name + '吃鱼'
      }
    }
    
  • TypeScript 抽象 抽象类- 它是提供其他类继承的基类,不能直接被实例化。
    用abstract关键字定义抽象类方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
    抽象方法只能放在抽象类中
// 抽象类和抽象方法用来定义标准, 标准:Animal这个类要求它的子类必须包含 eat 方法  
abstract class Animal {
  public name:string;
  constructor(name:string) {
    this.name = name 
  }
  abstract eat():any; // 抽象方法不包含具体实现并且必须在派生类中实现 
}

// var a = new Animal() /* 错误的写法,抽象类不能直接被实例化 */

class Dog extends Animal {
  constructor(name:string) {
    super(name)
  }
  // 抽象类的子类必须实现抽象类里面的抽象方法  
  eat() {
    console.log(this.name + '吃骨头')
  }
}
TypeScript 中的接口

接口的作用:在面向对象的变成中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到了一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规范这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescript中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
定义标准。

属性类接口

属性接口- 对json的约束

// 就是对传入对象的约束 - 属性接口
// 通过 interface 关键字定义接口 
interface FullName {
  firstName:string;  // 注意:分号结束 
  secondName:string;
}

// 接口的可选属性  
interface FullName {
  firstName:string;  // 注意:分号结束 
  secondName?:string;  // secondName 可传可不传 
}

function printName(name:FullName) {
  // 必须传入对象 firstName secondName
  console.log(name.firstName + '----' + name.secondName)
}
// printName({age:20,firstName: '张', secondName: '三'}) // 会出现编译报错 
let obj = {age:20,firstName: '张', secondName: '三'} 
printName(obj) //不会报错
函数类型接口

函数类型接口 - 对方法传入的参数以及返回值进行约束

// 加密的函数类型接口 
 interface encrypt {
   (key:string, value:string):string
 }

 var md5:encrypt = function(key:string,value:string):string {
   // 模拟操作  
   return key+value;
 }

 md5('name','张三')
可索引接口

可索引接口 - 数组、对象的约束 (不常用)

// 对数组的约束
interface UserArr {
  [index:number]:string
}

let arr:UserArr = ['aaa', 'bbb']

// 对对象的约束 
interface UserObj {
  [index:string]:string
}
let obj:UserObj = {name: '张三'}
类类型接口

对类的约束 - 和抽象类相似

interface Animal {
  name: string;
  eat(str:string):viod;
}
// implements 关键字,实现一个类 
class Dog implements Animal {
  name:string;
  constructor(name:string) {
    this.name = name
    eat() {
      console.log(this.name + '吃骨头')
    }
  }
}
接口扩展 - 接口可以继承接口
interface Animal {
  eat():void;
}

interface Person extends Animal {
  work():void;
}

class web implements Person {
  public name:string
  constructor (name:string) {
    this.name = name
  }
  eat() {
    console.log(this.name + '喜欢吃馒头')
  }
  work() {
    console.log(this.name + '写代码')
  }
}
泛型

泛型 - 软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。这样用户就可以以自己的数据类型来使用组件。
通俗理解 - 泛型就是解决 类、接口、方法的复用性,以及对不特定数据类型的支持。

泛型函数
// 只能返回string类型的数据 
function getData(value:string):string {
  return value
} 

//同时返回 string 和 number 类型  
// any 可以解决这个问题,但是放弃了类型检查
function getData(value:any):any {
  return  value 
}

//传入number类型必须返回number,传入string必须返回string 
// 泛型 - 可以支持不特定的数据类型 
// T 可以写成任意其他字母,但是三个必须保持一致,且必须大写 
function getData<T>(value:T):T {
  return value
}
// 使用 - 指定传入的number,返回的时候也是number
getData<number>(123);
泛型类

比如有个最小堆算法,需要同时支持返回数字和字符串两种类型。 通过类的泛型来实现

// 只支持数字类型 - 如果传入字符串 那么就会编译不通过  
class MinClass {
  public list:number[] = [];
  add (num:number) {
    this.list.push(num)
  }
  min():number {
    let minNum = this.lis[0]
    if(let i = 0; i < this.list.length; i++) {
      if(minNum > this.list[i]) {
        minNum = this.list[i]
      }
    } 
    return minNum
  }
}

// 类的泛型  - 
class MinClass<T> {
  publick list:T[]:[];
  add(value:T):void {
    this.list.push(value)
  }
  min():T {
    let minNum = this.lis[0]
    if(let i = 0; i < this.list.length; i++) {
      if(minNum > this.list[i]) {
        minNum = this.list[i]
      }
    } 
    return minNum
  }
}
// 实例化类 并且制定了类的T代表的类型是 number
let m1 = new MinClass<number>() 
// 实例化类 并且制定了类的T代表的类型是 string
let m2 = new MinClass<string>()
泛型接口
// 函数类型接口  
interface ConfigFn {
  (value1:string,value2:string):string
}

let setData:ConfigFn = function(value1:string,value2:string):string {
  return value1 + value2
} 
// 泛型接口  - 第一种写法
interface ConfigFn {
  <T>(value:T):<T>
}
let getData:ConfigFn = function<T>(value:T):T{
  return value
}
// 泛型接口 - 第二种写法  
interface ConfigFn<T> {
  (value:<T>):<T>
}
function getData<T>(value:<T>):<T> {
  return value
}
let myGetData:ConfigFn<string> = getData
练习

功能:定义一个操作数据库的库 支持 Mysql Mssql MongoDb
要求1: Mysql MsSql MongoDb 功能一样 都有 add update delete get 方法
注意:约束统一的桂法、以及代码的重用
解决方案:需要约束规范所以要定义接口,需要代码重用所以用到泛型
1、接口:在面对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范
2、泛型:解决类 接口 方法的复用性

interface DBI<T>{
  add(info:T):boolean;
  update(info:T, id:number):boolean;
  delete(id:number):boolean;
  get(id:number):any[];
}

//定义一个操作 mysql 数据库的类 
class MysqlDb<T> implements DBI<T>{
  add(info: any): boolean {
    throw new Error("Method not implemented.");
  }  
  update(info: any, id: number): boolean {
    throw new Error("Method not implemented.");
  }
  delete(id: number): boolean {
    throw new Error("Method not implemented.");
  }
  get(id: number): any[] {
    throw new Error("Method not implemented.");
  }
}

//定义一个操作mssql数据库的类 
class MsSqlDb<T> implements DBI<T>{
  add(info: any): boolean {
    throw new Error("Method not implemented.");
  }  
  update(info: any, id: number): boolean {
    throw new Error("Method not implemented.");
  }
  delete(id: number): boolean {
    throw new Error("Method not implemented.");
  }
  get(id: number): any[] {
    throw new Error("Method not implemented.");
  }
}

// 操作用户表  定义一个 User 类 和数据库表做映射  
class User {
  username:string | undefined;
  password:string | undefined;
}

let u = new User();
u.username="张三"
u.password='123456'

let oMysql = new MysqlDb<User>();  // 类作为参数来约束数据传入的类型
oMysql.add(u)
TypeScript 模块
模块

模块 - 关于术语的一点说明:请务必注意一点,TypeScript 1.5 里术语名已经发生了变化。“内部模块” 现在称作“命名空间”。 “外部模块 ”现在简称为“模块”模块在其自身的作用域里执行,而不是在全局作用域里。
这意味着定义在一个模块里的变量、函数、类等等再模块外部是不可见得,除非你明确的使用 exports 形式之一导出他们。相反,如果想使用其他模块导出的变量、函数、类、接口等的时候,你必须要导入它们,可以使用 import 形式之一。

  • export 暴露
let obj = {} 
let arr = []
export {obj, arr }
export function getData():void {

}
// export default  默认导出,只能在模块中使用一次
export deault getData
// 直接引入 
import getData from './index.ts'
  • import 导入
import{ getdata, obj , arr } from './index.ts'
// 将 getData 重命名为 get
import{ getData as get } from './index.ts'
TypeScript 命名空间
  • 命名空间 - 在代码量较大的情况下,为了避免各种变量名相冲突,可将相似功能的函数、类、接口等放置到命名空间内
    TypeScript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象。命名空间内的对象通过export导出

  • 命名空间 - 模块 的区别 命名空间:内部模块,主要用于组织代码,避免命名冲突
    模块:ts的外部模块的简称,侧重代码的附庸,一个模块里可能会有多个命名空间

//  命名空间  namespace  
namespace A {
  interface Animal {
    name:string;
    eat():void;
  }
  // 需要用 export 将命名空间内部的导出才能使用 
  export class Dog implements Animal {
    name: string;
    constructor(theName:string) {
      this.name = theName
    }
    eat() {
      console.log(this.name + '吃骨头')
    }
  }

  export class Cat implements Animal {
    name: string;
    constructor(theName:string) {
      this.name = theName
    }
    eat() {
      console.log(this.name + '吃鱼')
    }
  }

}

namespace B {
  interface Animal {
    name:string;
    eat():void;
  }
  export class Dog implements Animal {
    name: string;
    constructor(theName:string) {
      this.name = theName
    }
    eat() {
      console.log(this.name + '吃骨头')
    }
  }

  export class Cat implements Animal {
    name: string;
    constructor(theName:string) {
      this.name = theName
    }
    eat() {
      console.log(this.name + '吃鱼')
    }
  }

}
// A\B 两个空间里面的内容 名字一样 但不冲突
let d = A.Dog('狗')

TypeScript 装饰器

装饰器 - 装饰器是一种特殊的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为。
通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、方法、属性、参数的功能。

  • 常见的装饰器有
    • 类装饰器
    • 属性装饰器
    • 方法装饰器
    • 参数装饰器
  • 装饰器的写法
    • 普通装饰器(无法传参)
    • 装饰器工厂(可传参) 装饰器是过去几年中js最大的成就之一,已是 ES7 的标准特性之一
类装饰器

类装饰器 - 类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。传入一个参数

// 装饰器
function logClass (params:any) {
  console.log(params)
  //  params 就是当前类 
  // 为当前类 扩展属性
  params.prototype.apiUrl = '动态扩展属性';
  params.prototype.run = function() {
    console.log('我是一个run方法')
  }
}

//使用装饰器
@logClass
class HttpClient {
  constructor() {

  }
  getData() {

  }
}

// 实例化 
let http:any = new HttpClient() 
console.log(http.apiUrl)
hettp.run()
类装饰器 - 装饰器工厂

可以传参数的类装饰器

function logClass(params:string) {
  // params 就是传入的参数
  return function(target:any) {
    // target 就是当前使用装饰器的类 
    target.prototype.apiurl=params
  }
}

@logClass('hello')
class HttpClient {
  constructor() {

  }
  getData() {

  }
}

let http = new HttpClient() 
console.log(http.apiUrl) // hello
类装饰器 - 重载构造函数
function logClass (target:any) {
  return class extends target {
    apiUrl:any = '我是修改后的apiUrl';
    getData() {
      this.apiUrl = this.apiUrl + ' ---- ';
      console.log(this.apiUrl)
    }
  }
}

@logClass
class HttpClient {
  public apiUrl: string | undefined 
  constructor() {
    this.apiUrl = '我是构造函数里面的apiUrl' 
  }
  getData() {
    console.log(this.apiUrl)
  }
}

let http = new HttpClient() 
http.getData() // 我是修改后的apiUrl ----
属性装饰器

属性装饰器 - 属性装饰器表达式会在运行时当做函数被调用,传入下列2个参数。

  • 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 2.属性的名字
// 类装饰器 
function logClass(params:string) {
  return function(target:any) {

  }
}
// 属性装饰器 
function logProperty(params:any) {
  return function(target:any,attr:any) {
    // target 类的原型对象 
    // attr 属性的名字 - 本例中的url属性 
    // 将类中的 url属性值改为 传进来的 params
    target[attr] = params  
  }
}

@logClass('xxxx')
class HttpClient {
  @logProperty('https:www.baidu.com') // url的装饰器
  public url:string | undefined;
  constructor() {
    this.api = '我是构造函数里面的apiUrl
  }
  getData() {
    console.log(this.apiUrl)
  }
}
方法装饰器

方法装饰器 - 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
方法装饰会在运行时传入下列3个参数:

  • 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 2.方法的名称
  • 3.成员的属性描述符
// 装饰器 
function logMethod(params:any) {
  //params 参数的名字
  return function(target:any, methodName:any, desc:any) {
    // target 类的构造函数 
    // methodName 方法的名称 
    // desc 方法的描述
    // 可以拓展属性和方法
    target.apiUrl = 'xxxxx'
    target.run = function() {
      console.log('run')
    }
    // 修改装饰器的方法  把装饰器方法里面的传入的所有参数改为string类型 
    //  1.保存当前的方法 
    let oMethod = desc.value 

    desc.value = function(...args:any[]) {
      args = args.map((value) => {
        return String(value)
      })

      oMethod.apply(this,args)
    }

  }
}
class HttpClient {
  public url:any | undefined 
  constructor() {
    this.url = 'https://www.baidu.com'
  }
  @logMethod('http://www.google.com')
  getData(...args:any[]) {
    console.log(args)
    console.log('我是getData里面的方法')
  }
}

let http = new HttpClient()
http.getData(123, 'xxx') // ['123', 'xxx']  我是getData里面的方法
方法参数装饰器

方法参数装饰器 - 参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列3个参数:

  • 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 2.方法的名称
  • 3.参数在函数参数列表中的索引
function logParams(params:any) {
  return function(target:any,methodName:any,paramsIndex:any) {
    // target 构造函数 
    // methodName 方法的名称 
    // paramsIndex 参数的索引
    target.apiUrl = params
  }
}

class HttpClient {
  public url:any | undefined;
  constructor() {

  }
  getData(@logParams('xxxx') uuid:any) {
    console.log('我是构造函数中的getData')
  }
}

let http:any = new HttpClient() 
http.getData(11111)
装饰器执行的顺序

属性装饰器 -> 方法装饰器 -> 参数装饰器 -> 类装饰器
有多个同类装饰器的执行顺序是先2后1