Skip to content
TS基础

TS从入门到...哦,还没入门呢


前置🧀

类型声明

与js相比

TypeScript 代码最明显的特征,就是为 JavaScript 变量加上了类型声明

  • 变量名(标识符)+ 冒号 + 类型 如 let foo:string;
  • 变量的值应该与声明的类型一致
  • 变量只有赋值后才能使用

类型推断

类型声明并不是必需的,如果没有,TypeScript 会自己推断类型

函数

正是因为 TypeScript 的类型推断,所以函数返回值的类型通常是省略不写的

ts
function toString(num:number) {
  return String(num);
}

编译

  • TS编译将代码转为js
  • 编译时,会将类型声明和类型相关的代码全部删除,只留下能运行的 JavaScript 代码,并且不会改变 JavaScript 的运行结果

注意

因此,TypeScript 的类型检查只是编译时的类型检查,而不是运行时的类型检查。一旦代码编译为 JavaScript,运行时就不再检查类型了

类型基础

下面是三种特殊的类型

any类型

基本定义

any 类型表示没有任何限制(变量类型为any时,TS会关闭该变量的类型检查),该类型的变量可以赋予任意类型的值,也可以任意重新赋值(即使类型错误) ::: error 注意

ts
let x:any = 'hello';

x(1) // 不报错
x.foo = 100; // 不报错

变量x的值是一个字符串,但是把它当作函数调用,或者当作对象读取任意属性,TypeScript 编译时都不报错。原因就是x的类型是any,TypeScript 不对其进行类型检查 ::: 尽量避免使用any类型(否则为什么不用js呢),仅在特殊情况下any类型使用是合理的[1]

类型推断与any

TS无法推断的变量,默认为any类型

安全问题

尽管可以通过noImplicitAny编译选项禁止any类型推断,但是在 使用 let和var命令声明变量且不赋值也不指定类型时依然会推断为any类型[2] 所以使用let和var声明变量时,如果不赋值,就一定要显式声明类型,否则可能存在安全隐患

污染

一个any变量以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错 比如一个字符串的any变量可以赋值给number变量,这个时候不会报错

unknown类型

基本概念

为了解决any污染问题,unknown类型孕育而生,可视为严格版的any

  • 不能直接被赋值给其他类型
  • 不能直接调用该类型方法和属性
  • 可进行的符号运算有限,只能进行比较, ! , typeofinstanceof 运算符

类型缩小

注意上面是不能直接使用,那如何间接使用呢 经过typeof运算之后,unknown类型的值被确定被某种具体类型,才能被使用

类型缩小

这是一个示例

ts
let a:unknown = 1;

if (typeof a === 'number') {
  let r = a + 10; // 正确
}

never类型

为了保持与集合论的对应关系,以及类型运算的完整性,TypeScript 还引入了“空类型”的概念,即该类型为空,不包含任何值。

  • 不能被赋值
  • 可以赋值给任意其他类型
    ts
    function f():never {
      throw new Error('Error');
    }
    
    let v1:number = f(); // 不报错
    let v2:string = f(); // 不报错
    let v3:boolean = f(); // 不报错

使用场景

  • 在一些类型运算之中,保证类型运算的完整性
  • 不可能返回值的函数,返回值的类型就可以写成never(类似c的void)

更多类型

包装对象

所谓“包装对象”,指的是这些值在需要时,会自动产生的对象

概念

举个🌰

ts
'hello'.charAt(1)

上面示例中,字符串hello执行了charAt()方法。但是,在 JavaScript 语言中,只有对象才有方法,原始类型的值本身没有方法。这行代码之所以可以运行,就是因为在调用方法时,字符串会自动转为包装对象,charAt()方法其实是定义在包装对象上

使用new XXX可获取包装对象:

  • Boolean()
  • String()
  • Number()

由于包装对象的存在,导致每一个原始类型的值都有包装对象和字面量两种情况。

为了区分这两种情况,TypeScript 对五种原始类型分别提供了大写和小写两种类型。 大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象

建议声明时只使用小写类型(字面量)

Object 类型与 object 类型

  • Object类型:广义对象。所有可以转成对象的值,都是Object类型,这囊括了几乎所有的值
  • object类型:狭义对象,即可以用字面量表示的对象,只包含对象、数组和函数,不包括原始类型的值

事实上

除了undefined和null这两个值不能转为对象,其他任何值都可以赋值给Object类型

大多数时候我们使用对象类型,只希望包含真正的对象,不希望包含原始类型。所以,建议总是使用小写类型object,不使用大写类型Object

undefined 和 null 的特殊性

undefined和null既是值,又是类型。

  • 作为值,它们有一个特殊的地方:任何其他类型的变量都可以赋值为undefined或null。
  • 作为类型: undefined类型和null类型可以相互赋值
  • 所有未声明类型的对象赋予这两个值的时候会被推断为any类型[3]

编译选项

有时候我们不想要这种可以任意赋予其他变量的行为 比如上面的any类型的产生,又比如下面这个🌰

ts
const obj:object = undefined;
obj.toString() // 编译不报错,运行就报错

此时我们可以打开编译选项strictNullChecks,令undefined和null只能赋值给自身,或者any类型和unknown类型的变量

值类型

TypeScript 规定,单个值也是一种类型,称为“值类型”

ts
let x:'hello';

x = 'hello'; // 正确
x = 'world'; // 报错

特别的, TypeScript 推断类型时(对象除外[4]),遇到const命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型

这样推断是合理的,因为const命令声明的变量,一旦声明就不能改变,相当于常量。值类型就意味着不能赋为其他值

对于值类型的赋值要注意父子类型之间的赋值要求: 子类型不能被父类型赋值(反之可以),除非使用断言 ::: success 例子 比如值类型5是number的子类型

ts
let a:5;
let b:number=5;
a = b; //报错
b = a;//不会报错
a = (b) as 5; //不会报错

:::

类型的兼容

凡是可以使用父类型的地方,都可以使用子类型,但是反过来不行

联合类型

类型交集 多个类型组成的一个新类型,使用符号|表示 联合类型A|B表示,任何一个类型只要属于A或B,就属于联合类型A|B 联合类型可以与值类型相结合,表示一个变量的值有若干种可能

ts
let rainbowColor:'赤'|'橙'|'黄'|'绿'|'青'|'蓝'|'紫';

📢 信息

打开编译选项strictNullChecks后,其他类型的变量不能赋值为undefined或null。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法

ts
let name:string|null;

联合类型的第一个成员前面,也可以加上竖杠|,这样便于多行书写

ts
let x:
  | 'one'
  | 'two'
  | 'three'
  | 'four';

如果一个变量有多种类型,读取该变量时,往往需要进行类型缩小

交叉类型

类型子集 由于交叉类型为两个类型的交集,满足条件较为困难 所以一般仅作为表示对象的合成(为对象类型添加新属性)

ts
let obj:
  { foo: string } &
  { bar: string };

obj = {
  foo: 'hello',
  bar: 'world'
};

type 命令

type命令用来定义一个类型的别名

ts
type Age = number;

let age:Age = 55;
  • 别名不允许重名
  • 别名遵循块级作用域
  • type命令属于类型相关的代码,编译成 JavaScript 的时候,会被全部删除

typeof 运算符

TS中有两种typeof:

  • js原本的typeof: 用在值相关的部分,返回字符串
  • ts扩展typeof在类型上的使用: 它的操作数依然是一个值,但返回该值的类型

举个🌰

ts
let a = 1;
let b:typeof a;

if (typeof a === 'number') {
  b = a;
}

注意

JavaScript 的 typeof 遵守 JavaScript 规则,TypeScript 的 typeof 遵守 TypeScript 规则。它们的一个重要区别在于,编译后,前者会保留,后者会被全部删除。

由于编译时不会进行 JavaScript 的值运算,所以TypeScript 规定,typeof 的参数只能是标识符(变量),不能是需要运算的表达式

ts
type T = typeof Date(); // 报错

  1. 比如为适配旧的大型的js项目快速转移到ts时,使用any类型 ↩︎

  2. const在声明时必须赋值,显然不会有这个问题 ↩︎

  3. 可以理解为编译器不知道是undefined类型还是null类型,所以推断为any ↩︎

  4. 这是因为 JavaScript 里面,const变量赋值为对象时,属性值是可以改变的 ↩︎