淘先锋技术网

首页 1 2 3 4 5 6 7

类型系统

Flow: JavaScript 的类型检查器

TypeScript: JavaScript的超集(扩展集)

TypeScript是在JavaScript的基础上增加了一套强大的类型系统以及对ES6+的支持,最终会被编译为原始的JavaScript;
TypeScript的文件以 .ts结尾;

cosnt res = nums.find(i => i > 0);//这种情况下, TS无法明确 res 的类型: TS推断出 res 的类型为 -> number | undefined;


上面这种情况, 作为开发者, 就知道 res 一定会返回 number 类型的数据, 所以我们就可为 res 做类型断言;

类型断言有两种方式:

1. 使用 as 关键字:

   ```typescript
   let num1 = res as number;
   ```

2. 使用尖括号(<>): 在被断言的变量前面使用: 这种方式会与 JSX 中的标签产生语法冲突, 建议使用第一种;

   ```typescript
   let num2 = <number>res
   ```

- #### TS接口

- 定义: 用来约定一个对象的结构, 去使用接口就必须遵守接口的全部约定;在TS中, 接口用来约定对象中有哪些成员, 并且约定成员所对应的数据类型;

  ```typescript
  function printPost (Article) {//参数 Article 要求必须要有 title、content 属性, 但是这个要求是隐式的; 这里可以定义一个 Article 接口, 来表现出这个要求;
      console.log(Article.title);
      console.log(Article.content);
  }
  ```

- 声明接口:

  使用 interface 关键字来声明接口, 后面跟上接口名称, 名称后面是一个对象, 对象里面的键值用分号(;)分割

  ```
  interface Article {
  	title: string;
  	content: string;
  };
  ```

  接口中的成员分类:

  1. 普通成员

     ```typescript
     interface DefaultMember {
     	title: string;//title为普通成员
     	content: string;//content 为普通成员
     };
     //接口的使用
     let defaultMember: DefaultMember = {
         title: 'Hello TS',
         content: 'A JavaScript superset'
     };
     ```

  2. 可选成员: 在成员后添加问号(?) -> 表示成员可有可无

     ```typescript
     interface ChoosableMember {
         title: string;
         subTitle?: string;//subTitle 为可选成员
     };
     //接口的使用
     let choosableMember: ChoosableMember = {
         title: 'Hello Ts'// subTitle 可有可无
     };
     ```

  3. 只读成员: 在成员添加 readonly, 只读成员在初始化后就不能修改;

     ```typescript
     interface ReadonlyMember {
         title: string;
         readonly summary: string
     };
     //接口的使用
     let readonlyMember: ReadonlyMember = {
         title: 'Hello Ts',
         summary: 'A JavaScript Superset'
     };
     /* readonlyMember.summary = 'csa'; */ //这里语法报错; 
     ```

  4. 动态成员: 可以向接口中动态添加成员

     ```typescript
     interface Cache {
         [key: string]: string;//Cache 接口内的成员必须是 string 类型的键值;
     };
     //接口的使用
     const cache: Cache = {};
     cache.foo = 'value1';
     cache.bar = 'value2';
     ```


- #### TS 类

- 作用: 描述一类具体事物(方法)的抽象特征;

- 特征: 类的下面还可以细分很多子类: 子类一定会满足父类的所有特征, 然后再多出一些额外的特征;

- 使用: 我们不能去直接使用类, 而是去使用类的一些具体事物(方法);

  1. 在TS中, 需要明确 在类型中去声明类中所拥有的一些属性;

  2. 类中的属性必须要初始化: 可以用等号(=)的方式去赋值一个初始值, 也可以在构造函数 constructor 中动态赋值, 二者必须选其一;

  3. 在类的方法中, 可以使用 this.属性名 访问类中定义的属性;

     ```typescript
     class Person {
         name: string /* = 'lc' */; //name属性 通过等号直接初始化赋值;
         age: number;
         constructor (name: string, age: number) {
             this.name = name;//name属性 在构造函数 constructor 中动态赋值
             this.age = age;
         }
         sayHi (msg: string): void {
             //通过 this.属性名 访问类中定义的属性
             consle.log(`I am ${this.name}, ${this.age}`);
         }
     };
     ```

     

- ###### TS类的访问修饰符

  类中的每一个成员都可以用访问修饰符去修饰;

  1. public 修饰符: 表示被修饰的对象为共有的, 在TS中默认的访问修饰符为public;

  2. private 修饰符: 表示被修饰的对象为私用的, 私有的: 只能在类的内部去访问;

  3. protected 修饰符: 表示被修饰的对象为受保护的, 受保护的: 只能在此类中去访问该对象;

     ```typescript
     class Person {
         public name: string;//public 修饰符: 默认修饰符;
         private age: number;//private 修饰符: 私有的;
         protected gender: boolean;//protected 修饰符: 受保护的;
         
         constructor (name: string, age: number) {
             this.name = name; 
             this.age = age;
             this.gender = true;
         }
         
         sayHi (msg: string): void {
             console.log(`I an ${this.name}, ${msg}`);
             console.log(this.age);//age是私有属性, 只能在类中使用;
         }
     };
     const Tom = new Person('tom', 22);
     console.log(Tom.name);//tom
     console.log(Tom.age);//报错: 私有属性只能在内部使用;
     console.log(Tom.gender);//报错: 受保护的属性只能在此类中使用;
     ```

  4. private 修饰符 和 protected 修饰符的区别: 被 protected 修饰的对象是可以被继承的;

     ```typescript
     //这里的person 为上一个代码段的 Person
     class Student extends Person {
         constructor (name: string, age: number) {
             super(name, age);
             console.log(gender);//true //受保护的属性在此类中使用;
         }
     };
     ```

  5.  构造函数 constructor 的修饰符默认也是 public, 如果构造函数 constructor 被设置为私有的 private , 那么构造函数 constructor 就不能在外面用 new 实例化; 也不能被继承; 这种情况我们只能在类中创建一个静态方法, 让这个静态方法来实现实例化;

     ```typescript
     //这里的person 为上一个代码段的 Person
     class Student1 extends Person {
         private constructor (name: sting, age: num) {//现在的构造函数不能在外面 new 实例化
             super(name, age);
             console.log(this.gender);//true //这里是可以访问的
         }
         //创建一个静态方法来实现实例化
         static create (name: string, age: number) {
             return new Student1(name, age);
         }
     };
     const Jack = new Student1('jack', 19);//报错 //构造函数 constructor 是私有的 private;
     const Jack1 = Student1.create('jack', 19);//通过类中的静态方法实现实例化
     ```

- ###### 类的只读属性

  对于类的属性成员, 除了可以用访问修饰符来修饰, 还可以用 readonly 关键词设置类的属性成员为只读;

  如果类的属性成员已经被访问修饰符修饰, readonly 关键词只能跟在访问修饰符的后面;

  被 readonly 声明的属性, 等号初始化 和 构造函数 constructor 动态赋值只能选其一, 赋值后, 该属性就不允许被修改;

  ```typescript
  class newPerson {
      protected readonly height: number;//readonly 必须跟在protected 后面
      constructor (height: number) {
         this.height = height;//动态赋值 和 初始化赋值 只能选其一;
      }
  };
  ```

- ###### TS类与TS接口

  我们用接口来约束多个类的共同方法的类型, 不做方法的实现;

  ```typescript
  class MyPerson {
      eat (food: string): void {
          console.log(`优雅的进餐: ${food}`);
      }
      run (distance: number) {
          console.log(`直立行走: ${distance}`);
      }
  };
  class Animal {
       eat (food: string): void {
          console.log(`咕噜咕噜的吃: ${food}`);
      }
      run (distance: number) {
          console.log(`爬行: ${distance}`);
      }
  };
  //MyPerson 类和 Animal 类, 有相同的特性(方法), 我们可以用接口来对这两个特性(方法)做约束;
  interface EatAndRun {
      eat (food: string): void;
      run (distance: number): void;
  };
  //类用 implements 来实现接口的约束;
  //上面的代码可以修改为
  class MyPerson1 implements EatAndRun {
      eat (food: string): void {
        console.log(`优雅的进餐: ${food}`);
     }
     run (distance: number): void {
        console.log(`自立行走: ${distance}`);
     }
  };
  ```

- ###### 抽象类

  - 定义: 可以用来约束子类中必须要有某一个成员; 和接口的不同 -> 抽象类可以包含方法的具体实现, 接口不能包含方法的实现;
  - 语法: 声明抽象类: 在class 前面添加 abstract 关键词
  - 特征:
    1. 只能被继承, 不能用 new 实例化;
    2. 必须要用子类去继承抽象类;

  ```typescript
  abstract class NewAnimal {
  	eat (food: string) {
  		console.log(`咕噜咕噜的吃: ${food}`);
  	}
  	//在抽象类中还可以去定义一些抽象方法: 抽象方法需要用 abstract 关键词修饰, 抽象方法没有方法体;
  	abstract run (distance: number): void;
  };
  class Dog extends NewAnimal {
  	//继承了抽象类, 必须实现抽象类中的抽象方法, 不然后报错;
  	run (distance): void {
  		console.log(`四脚爬行: ${distance}`);
  	}
  };
  //抽象类的使用
  const d = new Dog();
  d.eat('大骨头');
  d.run(100);
  ```

- #### TS 泛型

- 定义: 在定义函数、接口、类的时候没有去指定具体的类型, 等到我们具体去使用时才指定类型的这样的特征;

  ```typescript
  function createNumberArray (length: number, value: number): number[] {
      const arr = Array(length).fill(value);
      //由于Array 默认创建的是 any 类型的数组; 这里的 Array 是一个泛型类;
      //这里就需要用泛型参数去传递一个类型去指定 Array(泛型类) 的数据类型;
      const arr1 = Array<number>(length).fill(value);
      return arr1;
  }
  const ResNumberArr = createNumberArray(3, 100);
  console.log(ResNumberArr);//[100, 100, 100]
  //这里的 createNumberArray 只能创建一个 number 类型的数组, 如果要创建一个 string 类型的数组, createNumberArray 做不到;
  ```

- 泛型使用: 在函数名后面去使用一对尖括号(<>), 尖括号内定义泛型参数(一般泛型参数用 T 作为名称), 函数中不明确类型的参数的类型注解就用 T 代替;

  ```typescript
  function createArray<T> (length: number, value: T): T[] {
      const returnArr = Array<T>(length).fill(value);
      return returnArr;
  }
  const stringArr = createArray<string>(3, 'foo');
  console.log(stringArr);//['foo', 'foo', 'foo']
const numberArr = createArray<number>(3, 100);
  console.log(numberArray);//[100, 100, 100]
  ```
  

- #### TS类型声明

在实际项目开发中, 我们会用到第三方的 npm 模块, 而这些模块并不一定是通过 TS 编写的, 所有有些模块所提供的成员就不会有强类型的体验;

```typescript
import { camelCase } from 'lodash';
//使用 camelCase 函数时没有类型注解; 这里可以使用 declare 关键词进行类型声明;
  • 语法: declare + 函数声明(function) + 要声明的函数 + 参数列表 + 函数的类型注解;
declare function camelCase (input: string): string;
//类型声明后 camelCase 就有类型限制了;