学习文档 : https://wangdoc.com/typescript/intro
学习阮一峰大神的TS,编写边记录重点。全文均已 TypeScript 教程为准。
简介
1.概述: TS 是 JS的超集(superset),是一个独立的类型系统。
2.概念:一组具有相同特征的值。类型是人为添加的一种编程约束合用法提示,可以帮助开发人员在开发过程中,得到更多的验证。提高代码质量,减少错误。
3. 类别:
动态类型 - JS,弱类型,可以随时更改数据类型
动态类型 - TS,不可改变类型
4. 优点:TypeScript 有助于提高代码质量,保证代码安全,更适合用在大型的企业级项目
《1. 有利于代码的静态分析,开发阶段运行静态检查,发现问题,降低线上风险。
(代码的静态分析:根据类型,不必运行代码,就可以确定变量的类型,从而推断代码有没有问题。)
《2. 有利于发现错误。
《3. 有更好的IDE支持,做到语法提示跟自动补全。
《4. 有代码文档(typedoc)
《5. 有助于代码重构
5. 缺点:兼容性问题、不一定适合那些小型的、短期的个人项目
《1. 丧失了动态代码的灵活性。
《2. 增加了编程工作量。
《3. 更改的学习成本
《4. 独立的编译步骤
《5. 兼容性问题
基础用法
- 类型声明:一律为在标识符后面添加“冒号 + 类型”。函数参数和返回值,也是这样来声明类型。
let foo:string;
变量 foo 后使用**冒号**,声明了类型为**string**
function toString(num:number):string{
return String(num)
}
函数toString()的参数num的类型是number。参数列表的圆括号后面,声明了返回值的类型是string
** TypeScript 规定,变量只有赋值后才能使用,否则就会报错
- 类型推断
TypeScript 会自己推荐类型。
let foo = 123;
变量 foo 并么有声明类型,由于他被赋值为一个数值,此时 ts 推荐 foo 的类型为 number。如果后面将 foo 改为其他类型的值,跟推断的不一样,ts 会报错。
foo = 'hello'; //报错
ts 也可以推断返回值
function toString(num:number){
return String(num)
}
toString() 没有声明类型,ts 推断为字符串。因为ts的类型推断,所以**函数返回值的类型通常是省略不写的**
ts 的设计思路,类型声明是可选的,你可以加,也可以不加。不加类型声明,依然是有效的ts代码,只是不能保证正确的推断出类型。这也是 js 代码都是合法的ts 代码的原因。
-
TS的编译
ts -> js 的过程 叫做编译(compile)。
ts 官方只提供编译器。
ts 的类型检查只是编译时的类型检查,代码编译为js 后,运行时不会在检查类型了。 -
值与类型
值:value 类型:type
“类型” 是针对 ”值” 的,“类型”是“值”的一个元属性。每一个值在TS里都是有类型的。比如, 3 是一个值,他的类型是 number
ts 只涉及类型。
ts项目包括两种代码:底层的“值代码”和上层的“类型代码”。“值代码” - > js 语法,“类型代码” -> ts 语法。
可以分离。ts编译过程实际是把“类型代码”全部拿掉,只保留“值代码”。 -
TypeScript Playground:官方的在线编译页面。
-
tsc 编译器:官方编译器。ts脚本的后缀名:.ts,tsc 就是将 .ts 脚本转变成 .js
6.1 安装 npm install -g typescript 版本:-v
6.2 帮助信息 -h --all
6.3 编译脚本 tsc app.ts 生成一个 app.js 的脚本文件。 tsc 1.ts 2.ts 3.ts 4.ts 编译多个
–outFile 将多个ts 编译成一个js : tsc file1.ts file2.ts --outFile app.js
–outDir 参数可以指定保存到其他目录。 tsc app.ts --outDir dist 会在 dist 生成 app.js
–target tsc --target es2015 app.ts 可以编译支持不同版本的 js
6.4 编译错误的处理 (tsc 命令的更多参数,详见《tsc 编译器》一章)
6.5 tsconfig.json
将 编译参数 写在配置文件 tsconfig.json 里。
{ "files":["1.ts","2.ts"], "compilerOptions":{ "outFile":"dist/app.js" }}
有了配置文件,编译时直接调用 命令就行: tsc
- ts-node 模块 非官方的 npm 模块,可以直接运行ts代码。
简单运行ts代码看效果, ts-node 是一个便捷的方法
全局安装ts-node
npm i -g ts-node
运行
ts-node script.ts
直接输入 tsc 不带参数 会给一个大于号,此时进入了 ts 的 REPL运行环境。可以逐步输入代码运行。
$ ts-node
> const twice = (x:string)=> x+x;
> twice('abc')
'abcabc'
> .exit
不安装 ts-node
npx ts-node script.ts
npx 在线调用 ts-node
any 类型,unknown 类型,never 类型
any 类型 - “顶层类型”(top type),涵盖了所有下层。
- 含义:any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。设置为any时,ts实际上会关闭这个变量的类型检查,只要句法正确,都不会报错。
- 类型推断问题:
《1. tsc --noImplicitAny xxx.ts 打开该选项,推断出any 类型就报错。
《2. let var 声明的变量,打开noImplicitAny,不赋值,不指定类型 不报错,建议用 const - 污染问题:污染其他具有正确类型的变量,把错误留到运行时。
unknown 类型 - 类型不确定,严格版的 any。‘顶层类型’,除any以外其他类型的全集。
- 跟 any 一样,所有的类型的值都可以分配给unknown类型。
- 跟 any 不同,不能直接使用。
let v:unknown = 123;
let v1:boolean = v; // 报错
let v2:number = v; // 报错
v 是 unknown 类型,赋值给any 和 unknown 以外的类型都会报错。这样就避免了污染问题。
- 不能直接调用 unknown 类型变量的方法和属性。
let v1:unknown = {foo:123};
v1.foo // 报错
let v2:unknown = 'hello';
v2.trim() // 报错
let v3:unknown = (n=0)=>n+1;
v3() // 报错
直接调用 unknown 类型变量的属性 和 方法 ,或者直接当做函数执行,都会报错
- unknown 类型变量能够进行的运算是有限的。
《1. 比较运算(== 、=== 、!= 、!== 、|| 、&& 、?)
《2. 取反运算(!)
《3. typeof
《4. instanceof - 使用 unknown 类型变量。 -> ‘类型缩小’ ,缩小 unknown 变量的类型范围,确保不会出错。
never类型:空类型->类型为空,不包含任何值。 – ‘底层类型’(bottom type)
let x:server;
x 的类型是never,不可能赋给它任何值,否则都会报错。
- 使用场景:类型运算之中,保证类型运算的完整性。
- 不可能返回值的函数,返回值的类型就可以写成never
- 变量有多种类型(联合类型)。处理所有可能的类型之后,剩余的情况就属于 never 类型。
- 特点:可以赋值给任意其他类型。
function f():never{
throw new Error('Error');
}
let v1:number = f(); //不报错
let v2:string = f(); //不报错
let v3:boolean = f(); //不报错
函数 f() 会抛出错误,所以返回值类型可以写成never,即不可能返回任何值。
- never 类型可以赋值给其他类型的原因:跟集合论有关系,空集是任何集合的自己。
总之,TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个。
类型系统
基本类型
-
概述:js 类型
· boolean
· string
· number
· bigint ----?
· symbol ----?
· object
· undefined
· null
ts 继承了 js 的类型设计,以上八种可以看做 ts 的基本类型。
注意:上面所有的类型名称都是小写字母,首字母大写的 Number、String、Boolean 等在JS语言中都是内置对象,不是类型名称。 -
boolean 类型:true 和 false 两个布尔值。
-
string 类型:包含所有的字符串
-
number 类型:包含所有整数和浮点数
-
bigint 类型:包含所有的大整数
const x:bigint = 123n;
const y:bigint = 0xffffn;
**bigint 与 Number 类型不兼容**
**bigint 类型的值 不能是整数和小数**
**注意,bigint 类型是 ES2020 标准引入的。如果使用这个类型,TypeScript 编译的目标 JavaScript 版本不能低于 ES2020(即编译参数target不低于es2020)。**
- symbol 类型:包含所有的 Symbol 值(需看一下 《Symbol》一章)
- object 类型:包含了所有的对象、数组和函数。
const x:object = { foo:123 };
const y:object = [1,2,3];
const z:object = (n:number) => n+1;
- undefined 类型,null类型。
《1. undefined :包含一个值undefined,表示未定义(即还未给出定义,以后可能会有定义)。
let x:undefined = undefined;
上述 第一个undefined 是 “类型” ;第二个undefined 是 ”值”
《2. null : 包含一个值 null, 表示为空(即此处没有值)。
**注意,如果没有声明类型的变量,被赋值为undefined或null,在关闭编译设置noImplicitAny和strictNullChecks时,它们的类型会被推断为any。**
// 关闭 noImplicitAny 和 strictNullChecks
let a = undefined; // any
const b = undefined; // any
let c = null; // any
const d = null; // any
如果希望避免这种情况,需要打开编译选项:strictNullChecks。
// 打开编译设置 strictNullChecks
let a = undefined; // undefined
const b = undefined; // undefined
let c = null; // null
const d = null; // null
打开编译设置strictNullChecks以后,赋值为undefined的变量会被推断为undefined类型,赋值为null的变量会被推断为null类型。
包装对象类型
- 包装对象的概念:
1.1 原始类型(primitive value):最基本的,不可再分的值。
特殊值:undefined 和 null
符合类型:object
· boolean
· string
· number
· bigint
· symbol
以上五种为原始类型,都有对应的包装对象(wrapper object)。所谓“包装对象”,值得是这些值在需要时,会自动产生的对象。
'hello'.charAt(1) // 'e'
字符串 hello 执行了 charAt() 方法。但是在JS中,只有对象才有方法。原始类型的值本身没有方法。上述代码运行,是因为在调用方法时,字符串会自动转为包装对象, charAt() 方法其实是定义在包装对象上。
以上五种包装对象中,symbol类型和bigint类型无法直接获取他们的包装对象( 即Symbol() 和 Bigint(),不能作为构造函数使用),剩下的三种可以:
· Boolean()
· String()
· Number()
以上三个构造函数,执行后可以直接获取某个原始类型值的包装对象。
**三个构造函数只有当做构造函数使用时(即带有 new 命令调用),才会返回包装对象。**
- 包装对象类型与字面量类型
因为包装对象的存在,导致每个原始类型的值都有包装对象和字面量 两种情况。
'hello'; // 字面量
new String('hello');// 包装对象
两个都是字符串。
区分:
· Boolean 和 boolean
· String 和 string
· Number 和 number
· Bigint 和 bigint
· Symbol 和 symbol
大写类型同时包括包装对象和字面量两种情况
小写类型只包含字面量
const s1:String = 'hello'; //正确
const s2:String = new String('hello'); // 正确
const s3:string = 'hello';// 正确
const s4:string = new String('hello'); // 报错
建议使用小写类型。因为绝大部分使用原始类型的场合,都是使用字面量,不适用包装对象。而且 TS 把很多内置方法的参数,定义为小写类型,使用大写类型会报错。
注意:目前在TS里面,symbol 和 Symbol 两种写法没有差异,bigint 和 Bigint 也是如此,建议始终使用 小写的。
Object 类型与 object 类型
- Object 类型
广义对象 - 所有可以转化对象的值,都是Object 类型,这囊括了几乎所有的值。
let object:Object; // || object:{};
object = true;
object = 'hi';
object = 1;
object = { foo :123 };
object = [1,2];
object = (a:number)=>a +1;
原始类型值、对象、数组、函数都是合法的 Object 类型。除了 undefined 和 null 以外,其他任何值都可以赋值给 Object 类型。
简写形式:{} //空对象
- object 类型
狭义对象 - 可以用字面量表示的对象 ,只包含对象、数组和函数,不包括原始类型的值。
let object:object; // || object:{};
object = { foo :123 }; // 对象
object = [1,2]; // 数组
object = (a:number)=>a +1; // 函数
// 原始类型值 报错
object = true; // 报错
object = 'hi'; // 报错
object = 1; // 报错
大多数时候,我们使用对象类型,只希望包含真正的对象,不希望包含原始类型,所以建议都使用小写。
** 无论大写还是小写,都只包含JS内置对象原生的属性和方法,不包括自定义在这两种类型里的属性和方法。
undefined 和 null 的特殊性
- undefined 和 null 即是值,又是类型。
- 作为值:任何其他的类型的变量都可以赋值为 undefined 或 null。
let age:number = 24;
age = null;
age = undefined;
JS 的行为是,变量如果等于 undefined 就表示还没有赋值;如果等于 null 就表示值为空。
所以 TS 允许了任何类型的变量都可以赋值为这两个值。
但是 当下编译不报错,运行时会报错。 undefined 不是对象,没有包含对象方法。
解决:
tsc --strictNullChecks app.ts 打开 --strictNullChecks 选项,undefined 和 null 就不能赋值给其他类型的变量(any 和 unknown 类型 除外)。
在 tsconfig.json 配置
{
"compilerOptions":{
"strictNullChecks":true
}
}
** 打开后,undefined 和 null 两种值也不能相互赋值了。只能赋值给自身,或者any类型和unknown 类型。
值类型
- 定义:单个值也是一种类型,成为 ‘值类型’。let x:‘hello’; x 的类型就是 字符串 ‘hello’
- const 没有注明类型,就会推断该变量是值类型。
联合类型
- 定义:多个类型组成的一个新类型,使用符号 | 表示。(A|B,可以属于A,也可以属于B)
let x:number|string;
x = 123;
x = 'abd'
// x 既可以是数字 也可以是 字符串 ,联合类型值的多少,表达了变量的取值范围有多少。
// 联合类型可以包含 空值
let aa:string|null;
aa = '2sr3'
aa = null
- 类型缩小(type narrowing):区分值的类型,进行下一步处理。
typeof | switch - 联合类型本身是一种 ‘类型放大’(type widening),所以处理时就需要 ‘类型缩小’(type narrowing)
交叉类型
- 定义:多个类型组成的一个新类型,用 & 表示。(A&B,同时属于A和B)
- 用途:表示对象的合成。为对象类型添加新属性
type A = { foo:number }
type B = A & { bar:number }
type 命令
- 定义:定义一个类型的别名。(就是给类型起了一个“艺名”)
- 优点:让类型的名字更有意义,增加代码的可读性,便利的使用复杂类型,方便修改变量类型。
- 重点:别名不允许重名。
- 别名的作用域是块级作用域。
- 别名支持表达式
- 别名允许嵌套
typeof 运算符
- 一元运算符,返回一个字符串,代表操作数的类型。
- 操作数 是一个值
- JS 只可能返回八种结果,都是字符串
- TS返回不是字符串,是 当前值的 TS 类型,只能用在类型运算之中,不能用在值运算。
- TS typeof 参数只能是标识符
- TS typeof 参数不能是类型
块级类型声明
- 类型可以声明在代码块(大括号)里面,并且只在当前代码块有效。
类型的兼容
- 某些类型可以兼容其他类型
- 子类型(subtype):类型A的值可以赋值给类型B,A 就成为B的子类型。
- 凡是可以用父类型的地方,都可以使用子类型,但是反过来不行。