TS从入门到...哦,还没入门呢
目录
参考
前置🧀
类型声明
与js相比
TypeScript 代码最明显的特征,就是为 JavaScript 变量加上了类型声明
- 变量名(标识符)+ 冒号 + 类型 如
let foo:string;
- 变量的值应该与声明的类型一致
- 变量只有赋值后才能使用
类型推断
类型声明并不是必需的,如果没有,TypeScript 会自己推断类型
函数
正是因为 TypeScript 的类型推断,所以函数返回值的类型通常是省略不写的
function toString(num:number) {
return String(num);
}
编译
- TS编译将代码转为js
- 编译时,会将类型声明和类型相关的代码全部删除,只留下能运行的 JavaScript 代码,并且不会改变 JavaScript 的运行结果
注意
因此,TypeScript 的类型检查只是编译时的类型检查,而不是运行时的类型检查。一旦代码编译为 JavaScript,运行时就不再检查类型了
类型基础
下面是三种特殊的类型
any类型
基本定义
any 类型表示没有任何限制(变量类型为any时,TS会关闭该变量的类型检查),该类型的变量可以赋予任意类型的值,也可以任意重新赋值(即使类型错误) ::: error 注意
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
- 不能直接被赋值给其他类型
- 不能直接调用该类型方法和属性
- 可进行的符号运算有限,只能进行比较,
!
,typeof
和instanceof
运算符
类型缩小
注意上面是不能直接使用,那如何间接使用呢 经过typeof运算之后,unknown类型的值被确定被某种具体类型,才能被使用
类型缩小
这是一个示例
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)
更多类型
包装对象
所谓“包装对象”,指的是这些值在需要时,会自动产生的对象
概念
举个🌰
'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类型的产生,又比如下面这个🌰
const obj:object = undefined;
obj.toString() // 编译不报错,运行就报错
此时我们可以打开编译选项strictNullChecks
,令undefined和null只能赋值给自身,或者any类型和unknown类型的变量
值类型
TypeScript 规定,单个值也是一种类型,称为“值类型”
let x:'hello';
x = 'hello'; // 正确
x = 'world'; // 报错
特别的, TypeScript 推断类型时(对象除外[4]),遇到const命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型
这样推断是合理的,因为const命令声明的变量,一旦声明就不能改变,相当于常量。值类型就意味着不能赋为其他值
对于值类型的赋值要注意父子类型之间的赋值要求: 子类型不能被父类型赋值(反之可以),除非使用断言 ::: success 例子 比如值类型5
是number的子类型
let a:5;
let b:number=5;
a = b; //报错
b = a;//不会报错
a = (b) as 5; //不会报错
:::
类型的兼容
凡是可以使用父类型的地方,都可以使用子类型,但是反过来不行
联合类型
类型交集 多个类型组成的一个新类型,使用符号|表示 联合类型A|B表示,任何一个类型只要属于A或B,就属于联合类型A|B 联合类型可以与值类型相结合,表示一个变量的值有若干种可能
let rainbowColor:'赤'|'橙'|'黄'|'绿'|'青'|'蓝'|'紫';
📢 信息
打开编译选项strictNullChecks后,其他类型的变量不能赋值为undefined或null。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法
let name:string|null;
联合类型的第一个成员前面,也可以加上竖杠|,这样便于多行书写
let x:
| 'one'
| 'two'
| 'three'
| 'four';
如果一个变量有多种类型,读取该变量时,往往需要进行类型缩小
交叉类型
类型子集 由于交叉类型为两个类型的交集,满足条件较为困难 所以一般仅作为表示对象的合成(为对象类型添加新属性)
let obj:
{ foo: string } &
{ bar: string };
obj = {
foo: 'hello',
bar: 'world'
};
type 命令
type命令用来定义一个类型的别名
type Age = number;
let age:Age = 55;
- 别名不允许重名
- 别名遵循块级作用域
- type命令属于类型相关的代码,编译成 JavaScript 的时候,会被全部删除
typeof 运算符
TS中有两种typeof:
- js原本的typeof: 用在值相关的部分,返回字符串
- ts扩展typeof在类型上的使用: 它的操作数依然是一个值,但返回该值的类型
举个🌰
let a = 1;
let b:typeof a;
if (typeof a === 'number') {
b = a;
}
注意
JavaScript 的 typeof 遵守 JavaScript 规则,TypeScript 的 typeof 遵守 TypeScript 规则。它们的一个重要区别在于,编译后,前者会保留,后者会被全部删除。
由于编译时不会进行 JavaScript 的值运算,所以TypeScript 规定,typeof 的参数只能是标识符(变量),不能是需要运算的表达式
type T = typeof Date(); // 报错