TS_02 初识类型系统

什么是类型

程序=数据结构+算法=各种格式的数据+处理数据的逻辑

数据是有格式(类型) 的

  • 数字、布尔值、字符
  • 数组、集合

程序是可能有错误的

  • 计算错误(对非数字类型数据进行一些数学运算)
  • 调用一个不存在的方法

不同类型的数据有不同的操作方式或方法,如:字符串类型的数据就不应该直接参与数学运算

动态类型语言 & 静态类型语言

动态类型语言

程序运行期间才做数据类型检查的语言,如:JavaScript

静态类型语言

程序编译期间做数据类型检查的语言,如:Java

静态类型语言的优缺点(java)

优点

  • 程序编译阶段(配合IDE、编辑器甚至可以在编码阶段)即可发现一些潜在错误,避免程序在生产 环境运行了以后再出现错误

  • 编码规范、有利于团队开发协作、也更有利于大型项目开发、项目重构

  • 配合IDE、编辑器提供更强大的代码智能提示/检查

  • 代码即文档

缺点

  • 麻烦
  • 缺少灵活性

静态类型语言的核心 : 类型系统

动态类型语言的优缺点

优点

  • 灵活
  • 方便

缺点

  • 运行程序时才能发现错误
  • 不能实现智能检查
  • 不利于团队开发

什么是类型系统

类型系统包含两个重要组成部分

  • 类型标注(定义、注解) - typing

  • 类型检测(检查) - type-checking

类型标注

  • 类型标注就是在代码中给数据(变量、函数(参数、返回值))添加类型说明,当一个变量或者函数
    (参数)等被标注以后就不能存储或传入与标注类型不符合的类型

  • 有了标注,TypeScript 编译器就能按照标注对这些数据进行类型合法检测。

  • 有了标注,各种编辑器、IDE等就能进行智能提示

类型检测

顾名思义,就是对数据的类型进行检测。注意这里,重点是类型两字。

类型系统检测的是类型,不是具体值(虽然,某些时候也可以检测值),比如某个参数的取值范围(1- 100之间),我们不能依靠类型系统来完成这个检测,它应该是我们的业务层具体逻辑,类型系统检测 的是它的值类型是否为数字!

类型标注

在TypeScript里面简单的类型标注的语法格式为

  1. //数据载体:类型

TypeScript 的类型标注,我们可以分为

  • 基础的简单的类型标注

  • 高级的深入的类型标注

基础的简单的类型标注

基础类型包括 string,number,boolean

标注语法

  1. let title: string = 'Ass';
  2. let n: number = 100;
  3. let isOk: boolean = true;

空和未定义

因为在 Null 和 Undefined 这两种类型有且只有一个值,在标注一个变量为 Null 和 Undefined 类 型,那就表示该变量不能修改了

  1. let a: null;
  2. // ok
  3. a = null;
  4. // error
  5. a = 1;

默认情况下 null 和 是所有类型的子类型。 就是说你可以把 null 和 其它类 型的变量

  1. let a: number;
  2. // ok
  3. a = null;

如果一个变量声明了,但是未赋值,那么该变量的值为 undefined ,但是如果它同时也没有标注类型 的话,默认类型为 any

  1. // 类型为 `number`,值为 `undefined`
  2. let a: number;
  3. // 类型为 `any`,值为 `undefined`
  4. let a
  1. 问题
  1. let a:number;
  2. a=null;
  3. a.toFixed(1)

小技巧:指定 strictNullChecks 配置为 true ,可以有效的检测 null 或者 undefined ,避 免很多常见问题

  1. let a:number;
  2. a = null;
  3. // error
  4. a.toFixed(1);

也可以使我们程序编写更加严谨

  1. let ele = document.querySelector('div');
  2. // 获取元素的方法返回的类型可能会包含 null,所以最好是先进行必要的判断,再进行操作
  3. if (ele) {
  4. ele.style.display = 'none';
  5. }

对象类型

内置对象类型

在 JavaScript 中,有许多的内置对象,比如:Object、Array、Date……,我们可以通过对象的 构造 函数 或者 类 来进行标注

  1. let a: object = {};
  2. // 数组这里标注格式有点不太一样,后面我们在数组标注中进行详细讲解
  3. let arr: Array<number> = [1,2,3];
  4. let d1: Date = new Date();

自定义对象类型

许多时候,我们可能需要自定义结构的对象,这个时候我们可以自定义

  • 字面量标注
  • 借口
  • 定义类或者是构造函数

字面量标注

  1. let a: {username: string; age: number} = {
  2. username: 'zMouse',
  3. age: 35
  4. };
  5. // ok
  6. a.username;
  7. a.age;
  8. // error
  9. a.gender;

优点 : 方便、直接

缺点 : 不利于复用和维护

接口:

  1. interface Person {
  2. username: string;
  3. age: number;
  4. };
  5. let a: Person = {
  6. username: 'zMouse',
  7. age: 35
  8. };
  9. // ok
  10. a.username;
  11. a.age;
  12. // error
  13. a.gender;

优点 : 复用性高
缺点 : 接口只能作为类型标注使用,不能作为具体值,它只是一种抽象的结构定义,并不是实体,没有具体功能实现

类与构造函数

  1. class Person {
  2. constructor(public username: string, public age: number) {
  3. }
  4. }
  5. // ok
  6. a.username;
  7. a.age;
  8. // error
  9. a.gender;

优点 : 功能相对强大,定义实体的同时也定义了对应的类型
缺点 : 复杂,比如只想约束某个函数接收的参数结构,没有必要去定一个类,使用接口会更加简单

  1. interface AjaxOptions {
  2. url: string;
  3. method: string;
  4. }
  5. function ajax(options: AjaxOptions) {}
  6. ajax({
  7. url: '',
  8. method: 'get'
  9. });

扩展:包装对象

这里说的包装对象其实就是 JavaScript 中的 String 、 Number 、 Boolean ,我们知道 string 类型 和 String 类型并不一样,在 TypeScript 中也是一样

  1. let a: string;
  2. a = '1';
  3. // error String有的,string不一定有(对象有的,基础类型不一定有) a = new String('1');
  4. let b: String;
  5. b = new String('2'); // ok 和上面正好相反
  6. b = '2';

数组类型

TypeScript 中数组存储的类型必须一致,所以在标注数组类型的时候,同时要标注数组中存储的数据 类型

  1. // 使用范型标注
  2. let arr1: Array<number> = [];
  3. // ok
  4. arr1.push(100);
  5. // error
  6. arr1.push('Ass');
  1. //普通标注
  2. let arr2: string[] = []; // ok arr2.push('开课吧');
  3. // error
  4. arr2.push(1);

元组类型

元组类似数组,但是存储的元素类型不必相同,但是需要注意:

  • 初始化数据的个数以及对应位置标注类型必须一致

  • 越界数据必须是元组标注中的类型之一(标注越界数据可以不用对应顺序 - 联合类型)

  1. let data1: [string, number] = ['开课吧', 100]; // ok
  2. data1.push(100);
  3. // ok
  4. data1.push('100');
  5. // error
  6. data1.push(true);

枚举类型

枚举的作用组织收集一组关联数据的方式,通过枚举我们可以给一组有关联意义的数据赋予一些友好的名字

  1. enum HTTP_CODE {
  2. OK = 200,
  3. NOT_FOUND = 404,
  4. METHOD_NOT_ALLOWED
  5. };
  6. // 200
  7. HTTP_CODE.OK;
  8. // 405
  9. HTTP_CODE.METHOD_NOT_ALLOWED;
  10. // error
  11. HTTP_CODE.OK = 1;

注意事项:

  • key 不能是数字
  • value 可以是数字,称为 数字类型枚举,也可以是字符串,称为 字符串类型枚举,但不能是其它值,默认为数字:0
  • 枚举值可以省略,如果省略,则: 第一个枚举值默认为:0. 非第一个枚举值为上一个数字枚举值 + 1
  • 枚举值为只读(常量),初始化后不可修改

字符串类型枚举

  1. enum URLS {
  2. USER_REGISETER = '/user/register',
  3. USER_LOGIN = '/user/login',
  4. // 如果前一个枚举值类型为字符串,则后续枚举项必须手动赋值 INDEX = 0
  5. }
  6. //小技巧:枚举名称可以是大写,也可以是小写,推荐使用全大写(通常使用全大写的命名方式来标注值为常量)

无值类型

表示没有任何数据的类型,通常用于标注无返回值函数的返回值类型,函数默认标注类型为: void

  1. function fn():void {
  2. // 没有 return 或者 return undefined
  3. }
  4. //在 strictNullChecks 为 false 的情况下, undefined 和 null 都可以赋值给 void ,
  5. //但是 当 strictNullChecks 为 true 的情况下,只有 undefined 才可以赋值给 void

Never类型

当一个函数永远不可能执行 return 的时候,返回的就是 never ,与 void 不同, void 是执行了return , 只是没有值, never 是不会执行 return ,比如抛出错误,导致函数终止执行

  1. function fn(): never {
  2. throw new Error('error');
  3. }

任意类型

有的时候,我们并不确定这个值到底是什么类型或者不需要对该值进行类型检测,就可以标注为 any 类型

  1. let a: any;
  2. //小技巧:当指定 noImplicitAny 配置为 true ,当函数参数出现隐含的 any 类型时报错
  • 一个变量申明未赋值且未标注类型的情况下,默认为 any 类型
  • 任何类型值都可以赋值给 any 类型
  • any 类型也可以赋值给任意类型
  • any 类型有任意属性和方法

注意:标注为 any 类型,也意味着放弃对该值的类型检测,同时放弃 IDE 的智能提示

未知类型

unknow,3.0 版本中新增,属于安全版的 any,但是与 any 不同的是:

  • unknow 仅能赋值给 unknow、any

  • unknow 没有任何属性和方法

函数类型

在 JavaScript 函数是非常重要的,在 TypeScript 也是如此。同样的,函数也有自己的类型标注格式

  1. //函数名称( 参数1: 类型, 参数2: 类型... ): 返回值类型
  2. function add(x: number, y: number): number {
  3. return x + y;
  4. }