Typescript 入门笔记

608 阅读10分钟

官网参考:www.tslang.cn/docs/handbo…

一、 ts中的数据类型

1. 布尔类型 (boolean)

最基本的数据类型之一,只有两个值为true/false

    let bol:boolean = true
    let bol:boolean = false
    let bol:boolean = 'aaa' // error

2. 数字类型 (number)

和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。

    let num: number = 123;
    let hexLiteral: number = 0xf00d;
    let binaryLiteral: number = 0b1010;
    let octalLiteral: number = 0o744;
    let octalLiteral: number = 0744; // error 8进制需要以0o开头,同严格模式
    let num: number = 'aaa' // error

3. 字符串类型 (string)

JavaScript程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用 string表示文本数据类型。 和JavaScript一样,可以使用双引号( ")或单引号(')表示字符串。

      let str: string = 'kaolaqi'
      let str: string = 123 // error

你还可以使用模版字符串,它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围( `),并且以${ expr }这种形式嵌入表达式

    let name: string = `Gene`;
    let age: number = 37;
    let sentence: string = `Hello, I'll be ${ age + 1 } years old next month.
        I'll be ${ age + 1 } years old next month.` // 支持换行

4. 数组类型 (array)

    // 方式一:在元素类型后面接上 `[]`,表示由此类型元素组成的一个数组
     let arr1: number[] = [1,2,3]
     let arr2: any[] = ['1',2,true]
    // 方式二:使用数组泛型,`Array<元素类型>`
     let arr3: Array<number> = [1,2,3]
     let arr4: Array<T> = [1,2,3]

5. 元组类型 (tuple)

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 stringnumberboolean类型的元组。

    let tuple: [string, number, boolean] = ['hahah', 111, true]
    let tuple: [string, number, boolean] = ['hahah', '111', true] // error 类型没有对应

6. 枚举类型 (enum)

enum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。

语法格式:
    enum 枚举名{
     标识符[=任意基本类型值],
     标识符[=任意基本类型值],
     ... ,
     标识符[=任意基本类型值]
    }
      enum Status {
          success = 1,
          error = 0
      }
      let flag: Status = Status.success
      console.log(flag) // 1
      
      enum Color {red, green, blue= 5, alpha}
      let color1:Color = Color.red
      let color2:Color = Color.alpha
      console.log(color1) // 0
      console.log(color2) // 6

      enum pay_result { success = 'success', error = 'error' }
      let res: pay_result = pay_result.success
      console.log(res) // 'success'

7. 任意类型 (any)

在我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量:

      let a: any = 1111
      a = 'aaa'
      a = true
      a = {}

8. nullundefined类型

TypeScript具有两种特殊的类型,nullundefined,它们分别具有值nullundefined默认情况下,类型检查器认为nullundefined可以赋值给任何类型。

注意: 通常VScode的typescript插件默认会勾选上 strictNullChecks,所以如果 tsconfig.json配置文件默认strictNullChecksfalse,VScode回自动读取插件的默认值,还是打开了typescript的strictNullChecks配置。

nullundefined是所有其它类型的一个有效值。 这也意味着,你阻止不了将它们赋值给其它类型,就算是你想要阻止这种情况也不行。

默认不开启--strictNullChecks标记
  let bol:boolean = true
  bol = null      // 不报错
  bol = undefined // 不报错

--strictNullChecks标记可以解决此错误:当你声明一个变量时,它不会自动地包含nullundefined。 你可以使用联合类型明确的包含它们

ts编译开启--strictNullChecks标记
  let bol:boolean = false
  bol = null       // 报错 Type 'null' is not assignable to type 'number'.
  bol = undefined  // 报错 Type 'undefined' is not assignable to type 'number'

处理方式:
  let bol:boolean | null | undefined = false
  bol = null      // 不报错
  bol = undefined // 不报错

9. void类型

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 一般用在当一个函数没有返回值时,其返回值类型是 void

    function fn1():void{
        console.log('aaaaa')
    }
    function fn2():number{
        console.log('aaaaa')
        return 111
    }

10. nuver类型

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

  let e: never;
  e = (() => { // e永远不会被赋值
    throw Error('错误')
  })()
  e = undefined || null // 报错

11. Object类型

表示非原始类型,也就是除了number,string,boolean,symbol,null或undefined之外的类型。

    function create(args: object){
      console.log(args)
    }
    create({ prop: 0 }); // OK
    create(null); // error  开启strictNullChecks模式 后null也不能赋值给Object类型
    create(42); // error
    create("string"); // error
    create(false); // error
    create(undefined); // error

12. 类型断言

有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。

    // a. 形式一: “尖括号”语法
      let someValue: any = "this is a string"
      let strLength: number = (<string>someValue).length
      console.log(strLength)

    // b. 形式二: as 语法
      let otherSomeValue: any = "this is a string";
      let otherStrLength: number = (otherSomeValue as string).length;
      console.log(otherStrLength)

二、ts中的函数

1. 函数的定义

    // a. 函数声明
    function run1(str:string): void {
      console.log(`函数执行输出:${str}`)
    }
    run1('你好')
    // b. 匿名函数表达式
    const run2 = function (age:number): string {
      return `我今年${age}岁~~`
    }
    run2(20)

2. 可选参数

    function fun1(firstname:string, lastname?: string): string {
      const str = `${firstname} ${lastname}`
      console.log(str)
      return str
    }
    fun1('lee', 'kaoalqi')

必须放在参数的最后

3. 默认参数

    function fun2(name:string, age: number = 20): string {
      const str = `${name}今年${age}岁了~`
      console.log(str)
      return str
    }
    fun2('kaolaqi')
    fun2('kaolaqi', 30)

4. 剩余参数

    function sum(num?: number, ...args:number[]):number {
      args.forEach(item => {
        num += item
      })
      console.log(num || 0)
      return num || 0
    }
    sum(0,1,2,3,4,5,6)
    sum(5)
    sum()

5. 函数重载

    function getInfo(name:string):string;
    function getInfo(age:number):string;
    function getInfo(info:string | number):string {
      if(typeof info === 'string'){
        return '姓名:' + info
      }else{
        return '年龄:' + info 
      }
    }
    getInfo('kaoalqi')
    getInfo(20)
    getInfo(true) // error

6. 箭头函数

箭头函数上下文指向定义时的环境

  setTimeout((str: string):void => {
    console.log(str)
  }, 10)

三、ts中的类

1. 类的定义

    class Person {
      name: string;
      constructor(name: string) {
        this.name = name
      }
      run(): string {
        return `${this.name}在运动~`
      }
    }
    let p1 = new Person('张三')
    p1.run()

2. ts实现继承

extends关键字实现对父类的继承

    class Programmer extends Person {
      age: number;
      constructor(name: string, age: number = 18){
        super(name)
        this.age = age
      }
      getUserInfo(): string{
        return `${this.name}今年${this.age}岁啦~`
      }
    }
    let p2 = new Programmer('李四', 20)
    p2.run()
    p2.name, p2.age
    p2.getUserInfo()

3. 类里面的修饰符

ts类里面有三种修饰符 和 一个静态方法|属性修饰符,用力对类的属性和方法做数据的隔离限制:

  • public: 公有类型 在类里面、子类、类外面(实例)都可以访问
  • protected: 保护类型 在类、子类里面可以访问,类外面(实例)不可以访问
  • private: 私有类型 在类里面可以访问,在子类和类外面(实例)不可以访问
  • static: 静态类型 在类的构造函数上使用,在父类和子类的构造函数上可以使用。类的实例上面不可访问
  • readonly: 只读修饰符,将属性设置为只读的。只读属性必须在声明时或构造函数里被初始化。
    class Animate {
      // 静态属性和静态方法
      static geWhere:string = '木子店';
      static go():string{
        return 'gogogo...'+ Animate.geWhere
      }

      // 属性修饰
      public name: string; // 属性|方法 不加修饰符,默认为 public
      protected status: number = 1;
      private level: string = '高级动物';
      
      // 构造函数
      constructor(name:string = '动物名', state: number = 1, level: string = '高级动物') {
        this.name = name
        this.status = state
        this.level = level
      }

      // 方法修饰
      public run(): string {
        return `${this.name}在运动~`
      }
      protected getInfo(): string {
        return `${this.name}${this.level}~`
      }
      private work(): string {
        return `${this.name}在工作~`
      }
    }

    class Dog extends Animate{
      constructor(name?: string, state?:number){
        super(name, state)
      }
      eat(): string{
        // console.log(this.level) // error 父类的私有属性,子类不可以使用
        console.log(this.getInfo())
        return `${this.name}在吃骨头~`
      }
    }
    let dog1 = new Dog('小黑', 1)
    console.log( dog1.name )
    // console.log( dog1.status ) // error 类内部的保护类型属性,类外面不可以使用
    console.log( dog1.run() )
    console.log( dog1.eat() )
    // console.log( dog1.getInfo() ) // error 类内部的保护类型方法,类外面不可以使用
    console.log(Animate.go())
    console.log(Dog.go())
    // console.log(dog1.go()) // error 类的静态方法,类实例不可以访问
    
    // 只读属性
    class Octopus {
        readonly name: string;
        readonly numberOfLegs: number = 8;
        constructor (theName: string, readonly age: number) {
            this.name = theName;
        } 
    }
    let dad = new Octopus("Man with the 8 strong legs", 12);
    dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.
    dad.age = 23; // 错误! age 是只读的.

4. 存取器

TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。下面来看类中使用 get和 set

let passcode = "secret passcode";
class Employee {
    private _fullName: string;
    get fullName(): string {
        return this._fullName;
    }
    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

5. 多态

类的多态指父类定义一个方法不实现,让继承他的子类去实现,每个子类都有不同的表现或处理逻辑,多态属于继承的一种。子类实现的方法覆盖父类中的属性和方法。

    class SomeAnimate {
      name: string;
      constructor(name: string) {
        this.name = name;
      }
      eat():void { // 具体吃啥不知道,继承它的子类去实现,每一个子类都有一个对应的eat方法
        console.log(`${this.name}吃东西,不知道吃啥,继承的子类自己定义`)
      }
    }
    class Pig extends SomeAnimate {
      constructor(name: string) {
        super(name)
      }
      eat(): string{
        return `${this.name}吃饲料`
      }
    }
    class Cat extends SomeAnimate {
      constructor(name: string) {
        super(name)
      }
      eat(): string{
        return `${this.name}吃老鼠`
      }
    }

6. 抽象类和抽象方法

ts中的抽象类,是提供其他类继承的基类,不能直接被实例化。用 abstract 关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现,abstract 抽象方法只能在抽象类里面,抽象类和抽象方法用来定义一个类的标准。

标准: 继承的这个派生类,必须实现抽象类中的所有抽象方法

    // abstract 定义抽象类 
    abstract class Article {
      title: string
      constructor(title: string) {
        this.title = title;
      }
      // abstract 定义抽象方法
      abstract getDesc(title: string): string
    }
    class Book extends Article{
      constructor(title: string) {
        super(title)
      }
      getDesc(title: string): string {
          return `${title}余华 活着`
      }
      getTitle(): string {
        return this.title
      }
    }

四、ts中的接口

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

1. 属性接口

属性接口,对一些json的约束,来规范限制不同场合下接受的json数据类型。

    // 限制传入函数的labelObj对必须包含有text属性
    function printLabelInfo(labelObj:{text:string}):void {
      console.log(labelObj.text)
    }
    printLabelInfo({text: '对json的约束'})
    printLabelInfo({data: '对json的约束'}) // error 没有规范要求的text属性

对批量方法传入的参数进行约束,是行为和动作的规范,对批量方法进行约束。 使用interface关键字定义接口格式为:

    interface 接口名 {
        参数名: 参数类型; 
        参数名: 参数类型; 
        ... ;
        参数名: 参数类型;
    }
      interface FullName {
        firstName: string;
        lastName: string;
      }
      function printName(name:FullName):void {
        console.log('大名为:' + name.firstName + ' ' +  name.lastName)
      }
      printName({firstName: 'lee', lastName: 'kaolaqi'})
      printName(1212321) // error, 参数属性不合格
      printName({firstName: 1111, lastName: 'kaolaqi'}) // error 参数的属性类型不对
      printName({firstName: 'lee', lastName: 'kaolaqi', age: 20}) // error 参数包含多余的属性
      const userInfo = { firstName: 'lee', lastName: 'kaolaqi', age: 20 }
      printName(userInfo) // OK,使用变量传入参数,只需要变量包含了要求的属性即可

接口的可选属性

    interface NameInfo {
      firstName: string;
      lastName?: string; // 接口的可选属性
    }
    function getUerInfo(nameObj: NameInfo) {
      console.log('大名为:' + nameObj.firstName + ' ' +  (nameObj.lastName || ''))
    }
    getUerInfo({firstName: 'lee', lastName: 'kaolaqi'})
    getUerInfo({firstName: 'lee'})

2. 函数接口

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

    interface encrypt {
      (key: string, value: string): string;
    }
    let md5:encrypt = function (key:string, value: string): string {
      // 模拟加密操作
      return `${key}---md5--${value}`
    }
    let sha1:encrypt = function (key:string, value: string):string {
      return `${key}---sha1--${value}`
    }
    console.log( md5('name', 'zhangsan') )
    console.log( sha1('name', 'lisi') )

3. 可索引接口

可索引接口: 对数组、对象的约束

    let numArr: number[] = [1,2,3]
    let stringArr: Array<string> = ['1','2','3']

    // 1. 可索引接口,对数组的约束
    interface DateArr {
      [index: number]: number;
    }
    let numArr2: DateArr = [1,2,3]

    // 2. 可索引接口,对对象的约束
    interface DateObj {
      [key: string]: any;
    }
    let data: DateObj = [1,'2',3]
    let obj: DateObj = {
      a: 1,
      b: 'aaa'
    }

4. 类类型接口

类类型接口: 对类的约束,和抽象类有点相似。 使用关键字 implements来指定当前定义的类及需要实现的接口

    interface AnimateType {
      name: string;
      eat(str: string): string;
    } 
    class Sheep implements AnimateType {
      name: string;
      constructor(name: string) {
        this.name = name
      }
      eat(str: string): string { // 实现的参数可以不一致,但是返回值要保持一致
        return `${this.name}${str}~`
      }
      run(): void {
        console.log(`${this.name}在遛弯~`)
      }
    }

5. 接口的扩展

使用 extends 可以让一个接口可以继承其他接口,是当前的接口也拥有继承接口的规则。

    interface Person {
      name: string;
      eat(thing: string): void;
    }
    interface SuperMan extends Person{
      specialty(something?: string): void;
    }
    class IronMan implements SuperMan {
      name: string;
      constructor(name: string) {
        this.name = name
      }
      eat(thing: string){
        console.log(`${this.name}${thing}`)
      }
      specialty(something?: string): void {
          console.log(something || '超人特长保密~')
      }
    }
    let tony = new IronMan('tony')
    console.log(tony.name)
    tony.specialty()

继承类并实现类接口

    class Programer {
      name: string;
      constructor(name: string) {
        this.name = name
      }
      eat(thing: string): void {
        console.log(`${this.name}${thing}`)
      }
    }
    class Web extends Programer implements SuperMan {
      name: string
      constructor(name: string) {
        super(name)
      }
      specialty(something?: string): void {
        console.log(something || '写js代码~')
      }
    }
    let webDevelop = new Web('小李')
    webDevelop.eat('汉堡🍔')
    webDevelop.specialty()

五、ts中的泛型

泛型:软件工程中,我们不见要创建一致的定义良好的api,同时也要考虑重用性。组建不仅能够支持当前的数据类型,同时也需要支持未来的数据类型;这在创建大型系统时为你提供类十分灵活的功能。在像c#和java这样的语言中,可以使用泛型来创建可重用的组建,一个组件可以支持多种类型的数据。这样用户就可以以自己的数据类型来使用组件。

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

1. 泛型定义

这个函数只能接受和返回string的数据类型, 如果我们要接受和返回string/number类型;使用any就放弃了类型检查,不合理。 使用泛型定义使用时的变量

    function getData1(value:string):string {
        console.log(value)
        return value 
    }

2. 泛型函数

    // 定义泛型函数
    function getData<T>(value: T): T{
        console.log(value)
        return value
    }
    // 指定类型直接执行函数
    getData<number>(111)
    getData<string>('aaa111')
    // getData<number>('111') //error
    
    // 指定泛型的类型,复制给一个新的函数
    let myGetData:getData<string> = getData;
    myGetData('11111')
    // myGetData(1111) //error

3. 泛型类

     class MinClass<T> {
        list: T[] = []
        add(value: T): void{
          this.list.push(value)
        }
        min(): T{
          let min = this.list[0]
          for(let i = 1; i < this.list.length; i++){
            if(min > this.list[i]){
              min = this.list[i]
            }
          }
          return min
        }
      }
      let numLs = new MinClass<number>()
      numLs.add(6)
      numLs.add(13)
      numLs.add(3)
      numLs.add(8)
      // // numLs.add('a') // error
      console.log(numLs.min())

      let strLs = new MinClass<string>()
      strLs.add('g')
      strLs.add('d')
      strLs.add('w')
      // numLs.add(111) // error
      console.log(strLs.min())

泛型可以帮助我们避免重复的代码以及对不特定数据类型的支持(类型校验),下面看看把类当作参数的泛型鳄梨的用法定义一个类,把类作为参数来约束数据传入的类型。例如node的ORM数据库模块typeORM

    class User {
      username: string | undefined;
      password: string | undefined;
      constructor(name: string, pwd: string){
        this.username = name
        this.password = pwd
      }
    }
    class MysqlDB {
      public list:User[] = [];
      add(user: User): Boolean{
        this.list.push(user)
        return true
      }
      getUserList(): User[]{
        return this.list
      }
    }
    const u1 = new User("kaolaqi", "123456")
    const db1 = new MysqlDB()
    db1.add(u1)
    db1.add({username: "kaolaqi", password: ""})
    // db1.add({username: "kaolaqi", password: "", a: 1111}) // error
    const u2 = {username: "kaolaqi", password: "", a: 1111}
    db1.add(u2)
    console.log( db1.getUserList() )

  // 使用泛型封装sqlDb的数据库模型
    class Userinfo {
      username: string | undefined;
      password: string | undefined;
    }
    class Article {
      title: string | undefined;
      desc: string | undefined;
      status: number | undefined;
      constructor(title: string, desc: string, status: number){
        this.title = title
        this.desc = desc
        this.status = status
      }
    }
    class SqlDB<T>{
      public list: T[] = [];
      add(data: T): void{
        this.list.push(data)
      }
      getData():T[]{
        return this.list
      }
    }
    let user1 = new Userinfo()
    user1.username = "kaolaqi"
    user1.password = "123456"
    let userModel = new SqlDB<Userinfo>()
    userModel.add(user1)
    console.log( userModel.getData() )

    let article1 = new Article('活着', '五六十年代的生活', 32)
    let articleModel = new SqlDB<Article>()
    articleModel.add(article1)
    console.log( articleModel.getData() )

4. 泛型接口

泛型接口定义:方式一

    interface ConfigFn{
      <T>(key: T, value: T): T;
    }
    let getInfo:ConfigFn = function<T>(value:T): T{
      return value
    }
    getInfo<string>('111')
    // getInfo<string>(111) // error
    getInfo<number>(1, 2)

泛型接口定义:方式二

    interface ConfigFn2<T>{
      (key: T, value: T): T;
    }
    function getInfo2<T>(key:T, value:T): T {
      return key
    }
    let strGetInfo2: ConfigFn2<string> = getInfo2
    strGetInfo2('111', '222')
    // strGetInfo2('111', 222) //error
    let numGetInfo2: ConfigFn2<number> = getInfo2
    numGetInfo2(111, 222)

六、ts模块化和命名空间

1. ts模块化

从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript也沿用了es6里面模块的概念。模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用export之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用米破额import之一。 TypeScript里使用模块与命名空间来组织代码的方法

任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出。

    // 导出接口
    export interface Fullname {
        firstname: string;
        lastname: string
    }
    // 到处变量|函数|类声明
    export const numberRegexp = /^[0-9]+$/;
    export function getName(firstName: string, lastName: string):string{
      return firstName + lastName
    }
    export class User {
      name:string;
      constructor(name: string) {
        this.name = name
      }
      run(): void {
        console.log('gogogo')
      }
    }
    // 默认导出
    export default User
    // 一起导出,可使用as重命名
    export { 
        Fullname,
        numberRegexp,
        getName as getFullName,
        User
    };
    // 重新导出
    export { ModuleA as RegExpBasedZipCodeValidator } from "./otherModule";

可以使用以下 import形式之一来导入其它模块中的导出内容。

    // 导入一个模块中的某个导出内容
    import { User } from "./ModuleA";
    // 可以对导入内容重命名
    import { numberRegexp as numExp } from "./ModuleA";
    // 将整个模块导入到一个变量,并通过它来访问模块的导出部分
    import * as allFn from "./ModuleA";
    // 具有副作用的导入模块
    import "./ModuleA";

每个模块都可以有一个default导出。 默认导出使用 default关键字标记;并且一个模块只能够有一个default导出。 需要使用一种特殊的导入形式来导入 default导出。

    const numberRegexp = /^[0-9]+$/;
    export default function (s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
    // 默认导入,导入的名称可以自定义
    import testStr from "./ModuleA";

类和函数声明可以直接被标记为默认导出。 标记为默认导出的类和函数的名字是可以省略的。

    export default class ZipCodeValidator {
        static numberRegexp = /^[0-9]+$/;
        isAcceptable(s: string) { 
            return s.length === 5 && ZipCodeValidator.numberRegexp.test(s); 
        }
    }
    // 默认导入,可以和导出时定义的名称不一致,导出的类型是可以省略的 
    import zipValidator from "./ModuleA";

export = 和 import = require()

CommonJS和AMD的环境里都有一个exports变量,这个变量包含了一个模块的所有导出内容。

CommonJS和AMD的exports都可以被赋值为一个对象, 这种情况下其作用就类似于 es6 语法里的默认导出,即 export default语法了。虽然作用相似,但是 export default 语法并不能兼容CommonJS和AMD的exports

为了支持CommonJS和AMD的exports, TypeScript提供了export =语法。

若使用export =导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块。

// ZipCodeValidator.ts
    let numberRegexp = /^[0-9]+$/;
    class ZipCodeValidator { 
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
    export = ZipCodeValidator;
// 导入
  import zip = require("./ZipCodeValidator");

2. 命名空间

在代码量较大的情况下,为了避免各种变量名冲突,,可将相似功能的函数、类、接口等放置到命名空间内,同java包,.net的命名空间一样,ts的命名空间也可以将代码包裹起来,只对外暴露需要在外部访问的对象。 命名空间同模块的却别:

命名空间:内部模块,主要用于组织代码,避免命名冲突

模 块:ts的外部模块的简称,侧重于代码的复用,一个模块里可能会有多个命名空间

在TypeScript里使用命名空间(之前叫做“内部模块”)来组织你的代码。命名空间里面的变量,如果没有使用 export 暴露出去,外部将无法访问到,相当于在全局作用域内部定义类一个内部私有空间,内部变量使用 export 暴露后可直接使用命名空间名称访问。

定义格式:

    namespace X {
        //空间内部代码
    }
    namespace Table{
      let userId:number = 0
      export class User {
        id: number = userId;
        username: string | undefined;
        password: string | undefined;
        constructor(name: string, pwd: string){
          this.id = ++userId
          this.username = name
          this.password = pwd
        }
      }
    }
    // 外部使用
    let user = new Table.User('李四', '9999999');
    console.log(Table.userId) // error 外部不能访问命名空间内部没有暴露的变量

当应用变得越来越大时,我们需要将代码分离到不同的文件中以便于维护。可以将一个命名空间下的代码才分成多个文件中编写,但还是属于同一个命名空间。

 // Validation.ts
    namespace Validation { 
        export interface StringValidator { 
            isAcceptable(s: string): boolean; 
        }
    }
    // 这里的 /// 使用来引入上面的命名空间,在上面的基础之上,
    // 再定义这个命名空间的其他功能代码,最终都是同一个命名空间。
    
    /// <reference path="Validation.ts" /> 
    namespace Validation { 
        const lettersRegexp = /^[A-Za-z]+$/;
        export class LettersOnlyValidator implements StringValidator { 
            isAcceptable(s: string) { 
                return lettersRegexp.test(s); 
            }
        }
    }

七、ts装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为。通俗的讲,装饰器就是一个方法,可以注入到类、方法、属性、参数上来扩展类、方法、属性、参数的功能。装饰器是过去几年中js最大的成就之一,已经是Es7的标准特性之一。

ES装饰器依然还是在实验性阶段,typescript默认还是不启用装饰器功能,所以使用ts的装饰器功能,需要在tsconfig.json配置文件中开启--experimentalDecorators标识。

常见的装饰器有: 类装饰器、属性装饰器、方法装饰器、参数装饰器

装饰器的写法: 普通装饰器(无法传参)、装饰器工厂(可传参),返回一个装饰器函数。

1. 类装饰器

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

    // 定义普通装饰器
    function logClass(target){
        console.log(target) // 普通的类装饰器函数接受的第一个参数就是装饰的类
        target.prototype.apiUrl = 'xxxxxx'
        target.prototype.run = function(){
          console.log('普通装饰器中定义的方法执行了')
        }
    }
    //给类添加普通装饰器,普通装饰器不可以传参数,添加时不可以带()执行装饰器函数
    @logClass      
    class HttpClient{
        consntructor(){}
        getData(){}
    }
    let http:any = new HttpClient()
    console.log(111222, http.apiUrl) // ‘xxxxxx’
    http.run() // '普通装饰器中定义的方法执行了'
    // 定义装饰器工厂(可传参)
      function logFactory(params:string){
        console.log(params)    // 装饰器工厂接受的第一个参数是添加装饰器传入的参数值
        return function(target: any){
          console.log(target)  // 装饰器工厂内部返回的函数接受的第一个参数就是装饰的类
          target.prototype.getUrl = function(){
            console.log(params)
          }
        }
      }
      @logFactory('www.baidu.com')      
      class HttpClient{
        consntructor(){

        }
      }
      let http:any = new HttpClient()
      http.getUrl() // 'www.baidu.com'

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数,如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。这样就可以使用类装饰器重载类的构造函数。

    function logClass(target:any){
      return class extends target {  // 重载类的构造函数
        apiUrl:string = '修改后数据'
        getData() {
          this.apiUrl = this.apiUrl + '-----'
          console.log(this.apiUrl)
        }
      }
    }

    @logClass
    class HttpClient{
      public apiUrl: string;
      constructor(){
          this.apiUrl = '自身构造函数里面的apiUrl'
      }
      getData(){
          console.log(this.apiUrl)
      }
    }
    let http:any = new HttpClient()
    http.getData() // '修改后数据-----'

2. 属性装饰器

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

  • a. 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  • b. 成员的字符串属性名称
      // 定义类装饰器
      function logFactory(params:string){
        console.log(params)    // 装饰器工厂接受的第一个参数是添加装饰器传入的参数值
        return function(target: any){
          console.log(target)  // 装饰器工厂内部返回的函数接受的第一个参数就是装饰的类
          target.prototype.getUrl = function(){
            console.log(this.url)
          }
        }
      }
      // 定义属性装饰器
      function logProperty(params:string){
        console.log(params)
        return function(target: any, key: string){
          console.log(target)   // 这里修饰的属性url是实例成员,target就是类的原型对象
          console.log(key)      // 这里第二个参数就会装饰器修饰的public属性名‘url’
          target[key] = params  // 装饰器重置url的public属性值为传入的params
        }
      }
      // 使用类装饰器
      @logFactory('www.baidu.com')      
      class HttpClient{
        // 使用属性装饰器
        @logProperty('http://www.baidu.com')
        public url:string;
        consntructor(){}
      }
      let http:any = new HttpClient()
      http.getUrl() // 'http://www.baidu.com'
  /**
      注意:装饰器的执行顺序,限制性内部的属性装饰器,再执行外部的类装饰器
  */

3. 方法装饰器

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

  • a. 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  • b. 成员的字符串方法名称
  • c. 成员的属性描述符
      // 定义方法装饰器
      function get(params:string){
        console.log(params)
        return function(target: any, key: string, desc: any){
          console.log(target)   // 这里修饰的getData方法是实例成员,target就是类的原型对象
          console.log(key)      // 这里第二个参数就是装饰器修饰的方法的名称字符串‘getData’
          console.log(desc)     // 这里第三个参数就是装饰器修饰的方法的属性描述符
          
          // 通过参数target扩张类的方法
          target.url = 'xxxxxx'
          target.run = function(){
              console.log(‘gogogo)
          }
          
          // 修改装饰器的方法,把装饰器方法里面传入的所有参数改为string类型
          cosnt oMethod = desc.value; // 保存之前的方法
          desc.value = function(...args: any[]){
              args = args.map(item => String(item))
              console.log(args) // 修正了参数之后,再执行之前的方法
              oMethod.apply(this,args)
          }
        }
      }
      class HttpClient{
        public url:string;
        consntructor(){}
        @get('http://www.baidu.com')
        getData(){
            console.log(this.url)
        }
      }

4.方法参数装饰器

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

  • a. 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  • b. 修饰的参数名称
  • c. 修饰的参数在函数参数列表中的索引序号
      function logParams(params:string){
        console.log(params)       // 'xxxxxx'
        return function(target: any, key: string, index: number){
          console.log(target)     // 这里修饰的getData方法是实例成员,target就是类的原型对象
          console.log(key)        // 这里第二个参数就是装饰器修饰的方法的名称字符串‘getData’
          console.log(index)      // 这里第三个参数就是装饰器修饰的参数的索引序号为0
        }
      }
      class HttpClient{
        public url:string;
        consntructor(){}
        getData(@logParams('xxxxxx') uuid:number, str: string){
            console.log(uuid)
        }
      }

5. 装饰器执行的顺序

// 定义类装饰器 2个
function logClass1(params:string){
  return function(target: any){
    console.log('类装饰器1')
  }
}
function logClass2(params:string){
  return function(target: any){
    console.log('类装饰器2')
  }
}
// 定义属性装饰器 2个
function logProproty1(params:string){
  console.log(`属性装饰器1111传参:${  params}`)
  return function(target: any, key: string){
    console.log('属性装饰器1')
  }
}
function logProproty2(params:string){
  console.log(`属性装饰器2222传参:${  params}`)
  return function(target: any, key: string){
    console.log('属性装饰器2')
  }
}
// 定义方法装饰器 2个
function logMethods1(params:string){
  return function(target: any, key: string, desc: any){
    console.log('方法装饰器1')
  }
}
function logMethods2(params:string){
  return function(target: any, key: string, desc: any){
    console.log('方法装饰器2')
  }
}
// 定义参数装饰器 3个
function logParams1(params:string){
  return function(target: any, key: string, index: any){
    console.log('参数装饰器1')
  }
}
function logParams2(params:string){
  return function(target: any, key: string, index: any){
    console.log('参数装饰器2')
  }
}
function logParams3(params:string){
  return function(target: any, key: string, index: any){
    console.log('参数装饰器3')
  }
}

// 使用类装饰器
@logClass1('www.baidu.com')  
@logClass2('')      
class HttpClient4{
  // 使用属性装饰器
  @logProproty1('11111')
  @logProproty2('22222')
  public url:string;
  
  constructor(url: string){
    this.url = url
  }

  // 使用方法装饰器
  @logMethods1('')
  @logMethods2('')
  getData(@logParams3('') arg1:any, @logParams1('') @logParams2('') arg2:any, ){ // 使用参数装饰器
    console.log(arg1, arg2)
  }
  
  @logProproty1('33333')
  @logProproty2('44444')
  public method:string;
}
let http:any = new HttpClient('https://www.baidu.com')
console.log(http.url)

执行结果:

    属性装饰器1111传参:11111
    属性装饰器2222传参:22222
    属性装饰器2
    属性装饰器1
    参数装饰器2
    参数装饰器1
    参数装饰器3
    方法装饰器2
    方法装饰器1
    属性装饰器1111传参:33333
    属性装饰器2222传参:44444
    属性装饰器2
    属性装饰器1
    类装饰器2
    类装饰器1
    https://www.baidu.com

结论:

  • 属性 > 方法参数 > 方法 > 类
  • 同一个属性/方法/参数/类 有多个装饰器同时修饰,装饰器方法里面的操作会先执行后面的
  • 不同的属性/方法/参数/类的装饰器,会从上往下执行
  • 装饰器工厂内部的操作会从上往下依次执行,返回的装饰器函数内部操作会先执行后面的