「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」
前言
写惯了动态语言 js
的前端开发,突然切换使用 Typescript
静态类型语言,编译时就会检查类型,这样可能会一下子不习惯。下面我将通过类比大家熟悉的语言来具体介绍 Typescript
的预备知识。接下来我们一起来愉快地渡过预备期吧~
预备知识
1、TS与JS的关系
Typescript
是 JavaScript
的超集,它提供了所有 JavaScript
的特性,并且在其上层增加了 Typescript
类型系统。
Typescript 类型系统,被设计为”可选的“,这就意味着:所有合法的 JavaScript 代码都是合法的 Typescript 代码。
2、TS的编译过程
我们可以通过对比 C++ 编译过程来理解 TS 的编译过程,如下图所示:
注意:”TS的类型检查“ 与 ”生成JS“ 是2个独立的过程。无论类型检查是否出错都会生成 JavaScript 代码!
3、类型系统
一般编译型语言都有其类型系统,可分为2类:一种是结构类型系统;一种是名义类型系统。下面我们具体来看看吧。
结构类型系统
结构类型系统(Structural Type System
),是通过类型的实际结构确定两个类型是否相等或兼容。(采用该类型系统主要有:TypeScript
,Haskell
,OCaml
,Go
,...)
class Foo {
public hi(){
console.log("hi")
}
}
class Bar {
public hi(){
console.log("hello")
}
}
const a : Foo = new Foo()
const b : Bar = a
b.hi() //输出:hi
复制代码
上述ts
代码:创建Foo
的实例并赋值给 a
,a
又 赋值给类为 Bar
的 b
,b
可以直接执行 Foo
类的 hi
方法,正常工作不报错。可见类 Foo
和 Bar
,虽然类名不一样,但结构相同,都有hi
的方法,因此属于相同的类。
名义类型系统
名义类型系统(Nominal Type System
),是通过类型的名称确定两个类型是否相等。(采用该类型系统主要有:C
, C++
, Java
, C#
, Rust
, ...)。
#include <iostream>
class Foo {
public:
void hi() { std :: cout << "Hi" << std :: endl;}
};
class Bar {
public:
void hi() { std :: << "Hello" << std::endl;}
}
int main (){
Foo foo;
Foo & fooRef = foo; //works!
//Error:type 'Bar' cannot
//bind to a value of unrelated type 'Foo'
Bar & bar = foo;
return 0;
}
复制代码
在上述 C++
代码:虽然 Foo
和 Bar
结构相同,但是类名不同,不能完成赋值。
4、类型注解
不同类型的语言有着不同的类型注解语法。下面我们来看看以下demo:
// C++
int printf (const char*, ...)
# Objective-c
- (id)initWinthInt:(int)value;
//Julia
commission(sale::Int , rate::Float64)::Float64
#TypeScript
function log(message: string):void
复制代码
- C++ 放在参数/函数前面
- Objc 放在前面,加括号
- Julia 放在后面,加双冒号
- TS 也放在后面,加单冒号
5、类型与集合的关系
我们在上学的时候,数学有老师有讲过集合的概念。其主要分为几类:空集、单元素集合、有限集合、无限集合、全集。我们可以类比集合来学习 TS 类型。
空集
never = Ø = {}
复制代码
单元素集合
Null = {null}
Undefined = {undefined}
Literal Type(字面量类型,'a',1,true)
复制代码
有限集合
Boolean = {true,false}
复制代码
无限集合
String = {'a','b', ... 'hello', ...}
Symbol = {Symbol(...), ...}
BigInt = {..., -1n,0n,1n,...}
Number = {-Infinity,-(2^53 - 1),...0,...+(2^53 - 1),Infinity} 和 NaN
复制代码
全集
unknown = Universal set
复制代码
6、类型联合与交叉
TS 联合与交叉类型也可以通过数学集合来类比学习:
名字 | 联合类型(Union Types) 集合并集(Union) |
交叉类型(Intersection Types) 集合交集(Intersection) |
---|---|---|
图示 | ||
TS 写法 | A | B(A或B) | A & B(A与B) |
数学写法 | A U B(A并B) | A ∩ B(A交B) |
7、类型别名
TS 类型别名也可以通过类比的方式学习:
- JS中:我们可以使用let, const, var声明变量或常量。
- TS中:我们可以使用type为类型声明别名。
// Value Space
let ID = 9527
const PI = 3.14
var myPI = PI
// Type Space
type ID = string
type User = {
id:ID;
name:string
}
//Error: Duplicate identifier 'User'
type User = {}
//块级作用域
{
//OK
type User = {
id:number;
name:string
}
}
复制代码
注意:TS 类型别名和let 变量类似,有着块级作用域。因此,同一个作用域内不能重名使用,不然会报错的。
8、类型拓宽、收窄
类型拓宽
当你用字面量赋值给let,var变量时,TS不用字面量类型作为该变量的类型,而是从字面量类型拓宽到响应的更宽泛的类型,这个过程叫做类型拓宽。
类型收窄
在某些情况下,TS可以更加确定变量的类型,此时它会将变量类型收窄。
TS试图在类型确定性与灵活性之前取得平衡 TS提供一系列方法来帮助收窄类型,以提高类型的确定性: null check,as const,instanceof,typeof属性检查、Tagged Union 、用户类型守卫(User-defined Type Guard)、代码流分析。
9、值空间与类型空间
- 所谓的类型空间是编译期存在的各种类型,这个空间是编译器tsc创建的。编译成js后有可能被擦除。
- 值空间是js的V8引擎在运行的时候创建的,里面存在了各种类型的值
如何判断符号是在哪个空间?
- 转译后消失的符号 -> 类型空间
- 作为类型注解.别名的符号 -> 类型空间
- 类型断言后的符号 -> 类型空间(target as/is HTMLElement)
- const let var后面的符号 -> 值空间
- class enum namespace 后的符号 -> 值空间+类型空间
常用关键字、运算符在值空间和类型空间的不同含义
-
this关键字
在值空间,this指向比较复杂
在类型空间,this可以作为类方法的返回值来实现链式调用
-
& | 运算符
在值空间表示"按位与" 和 "按位或"
在类型空间表示类型的交叉和联合
-
const
在值空间用来声明常量
在类型空间与as连用 即as const 常量断言 收窄类型
-
extends
在值空间用于定义子类
在类型空间用来进行类型约束或者接口继承
-
in
在值空间用于for循环和判断属性是否存在
在类型空间用于映射类型的key的声明
总结
上述我们一起学习完 Typescript 的预备知识,这时候对 TS 有了个大概的认知,那么后续我们继续学习 TS 正式知识就没那么困难啦!