文章输出主要来源:拉勾大前端高新训练营(链接)。小哥哥小姐姐请不要嫌弃啰嗦,下面肯定都是干货。
1 JavaScript语言类型系统
1.1 强类型与弱类型(类型安全)
强类型:
- 语言层面限制函数的实参类型必须与形参类型相同
- 不允许任意的数据隐式类型转换
弱类型:
- 语言层面不会限制实参的类型
- 允许任意的数据隐式类型转换
1.2 静态类型与动态类型(类型检查)
**静态类型:**变量声明时候类型是明确的,声明之后类型不允许再修改
动态类型: 运行过程中变量类型才能被明确,而且变量的类型随时可以被改变
动态类型的变量是没有类型的但是变量中存的值是有类型的。
强类型的优势
- 错误可以更早暴露
- 代码更智能,编码更准确
- 重构更牢靠
- 减少不必要的类型判断
2 Flow
通过类型注解对变量做标注。
2.1 基础使用
-
安装
flow:yarn add flow-bin -D -
js中引入flow的声明:
// @flow -
为js变量添加类型注释
function add (a: number, b: number) { return a + b; } add(100, 100);
add('100', '100');
- 使用flow命令进行检查`yarn flow` or `npx flow`
### 2.2 编译移除flow类型注释
js代码中变量添加的类型注释不是js的标准语法,只能在开发阶段使用,还需进行编译移除类型注释才能正确运行js代码。
**1. flow官方移除工具:** `yarn add flow-remove-types -D`
使用方法: `yarn flow-remove-types source -d target`, 例: `yarn flow-remove-types ./flow -d dist`
**2. 使用babel进行移除:**`yarn add @babel/core @babel/cli @babel/preset-flow -D`
添加`.babelrc`配置文件
```json
{
"presets": ["@babel/preset-flow"]
}
使用命令与flow官方工具一致:yarn babel ./flow -d ./dist
2.3 flow类型检查编辑器支持
vscode插件Flow Language Support保存代码后才会进行检查,体验会慢
2.4 类型注解
变量标注
let num: number = 100
标注函数类型
function foo():string {
return 'hello'
}
// 无返回值,标注void
function bar():void {
console.log('hello world');
}
2.5 类型
1. flow原始类型
对应于javascript中的原始类型
const a: string = 'hello';
const b: number = Infinity // NaN // 100;
const c: boolean = false; // true;
const d: null = null;
const e: void = undefined;
const f: symbol = Symbol();
2. 数组类型
通过Array<T>泛型标注进行表示,或T[]进行表示
const arr1: Array<string> = ['a', 'b'];
const arr2: number[] = [1,2,3];
固定长度的数组/元祖
const foo: [string, number] = ['a', 1];
3. 对象类型
通过对象所有属性声明类型为对象声明类型,?为可选项
const obj1: {
foo: string,
bar: number,
baz?: boolean
} = {
foo: 'hello',
bar: 123
}
通过添加索引签名限制对象属性类型
const obj2: {[string]: string} = {}
4. 函数类型
通过类似箭头函数的索引签名定义函数类型
function fn1(callback: (string, number) => void) {
callback('string', 100);
}
5. 特殊类型
字面量类型:字面量类型通常配合|联合类型(或类型)一起使用
let a: 'foo' | 'bar' | 'baz';
a = 'foo'
通过type定义新的类型
type StringOrNumber = string | number;
type Person = {
name: string;
age: number;
}
const tom: Person = {
name: 'tom',
age: 18
}
maybe类型?
通过?定义可选的变量,相当在具体类型上扩展了null和undefined的值
const name: ?string = undefined;
const name1: ?string = null;
const name2: ?string = 'tom';
6. mixed与any
mixed与any都可以接收任意类型
mixed是所有类型的联合类型
function fn(value: mixed) {
console.log(value);
}
fn(100);
fn('hello');
fn({name: 'tom'});
any也可以接收任意类型
function fn1(value: any) {
console.log(value);
}
fn1(100);
fn1('hello');
fn1({name: 'tom'});
mixed与any的区别:
mixed是强类型,声明为mixed类型的变量不能随便调用例如字符串的方法str.substr();
any是弱类型,可以任意调用其他类型的方法,在语法层面不报错
2.6 运行环境api
在不同环境flow提供了不同环境的类型定义文件
3 TypeScript
3.1 安装与简单使用
安装:yarn add typescript -D
const hello = (name: string) => {
console.log(`hello ${name}`);
}
hello('tom');
编译:tsc filename.ts
编译完成生成js后可用node直接运行。
配置文件:yarn tsc --init or npx tsc --init生成tsconfig.json配置文件
使用tsc命令运行某个文件配置文件不生效,直接使用tsc命令编译整个项目会生效
3.2 Js 6种原始类型在ts中的应用
基本与flow一致
注意:
- 严格模式下
string,number,boolean不可为空null - 非严格模式下
string,number,boolean可以为空null - 严格模式下只能为
undefined,非严格模式下可以为空null - 如果ts配置文件target为
es5,则Symbol类型报错(使用任何ES6+新定义的内容都会报错),因为默认引用的是lib.es5.d.ts库- 解决方案1:将
target改为es2015 - 解决方案2: 在
lib配置数组中添加ES2015,此时原本的库被覆盖console.log报错,还需引入DOM库,TS中将DOM跟BOM API都定义在了DOM库中
- 解决方案1:将
const a: string = 'hello';
const b: number = 100; // NaN; // Infinity
const c: boolean = true; // false;
// 严格模式下string, number, boolean不可为空null
// const aa: string = null;
// const bb: number = null; // NaN; // Infinity
// const cc: boolean = null; // false;
// 非严格模式下string, number, boolean可以为空null
// 严格模式下只能为undefined,非严格模式下可以为空null
const e: void = undefined;
const f: null = null;
const g: undefined = undefined;
const h: symbol = Symbol()
3.3 TS 中的object类型
ts中的object类型不单指普通对象,它代表的是所有非原始类型,例如:对象,数组 ,函数
普通对象通过类似对象字面量的方式进行类型声明
const obj: {
name: string;
age: number;
} = {
name: 'tom',
age: 18,
}
3.4 数组类型
与flow中的定义方式基本一致。
- 通过泛型接口定义:
const arr1: Array<number> = [] - 通过类型+
[]定义:const arr2: number[] = []
3.5 元组类型
通过类似数组字面量方式定义元组,元组本质也是个数组
const tuple: [number, string] = [1, 'tom'];
3.6 枚举类型
enum PostStatus {
Draft = 0, // 枚举值默认从0开始,可以自定义开始的值,后续会自增
Unpublished = 1,
Published = 2
}
枚举的结果会被编译为双向键值对
var PostStatus;
(function (PostStatus) {
PostStatus[PostStatus["Draft"] = 0] = "Draft";
PostStatus[PostStatus["Unpublished"] = 1] = "Unpublished";
PostStatus[PostStatus["Published"] = 2] = "Published";
})(PostStatus || (PostStatus = {}));
如果枚举值不为数字,不会被编译为双向键值对
// 定义
enum PostStatus2 {
Draft = '0',
Unpublished = '1',
Published = '2'
}
// 编译后
var PostStatus2;
(function (PostStatus2) {
PostStatus2["Draft"] = "0";
PostStatus2["Unpublished"] = "1";
PostStatus2["Published"] = "2";
})(PostStatus2 || (PostStatus2 = {}));
常量枚举会被编译为普通对象,不能通过枚举值查找key,定义方式为
const enum PostStatus {
Draft,
Unpublished,
Published
}
3.7 函数类型
通过函数声明方式定义函数时的函数类型声明
function func1(a: number, b: string): string {
return 'hello world';
}
通过?可以声明可选参数,也可为参数赋默认值,这两种方式都要放在参数列表末尾
箭头函数形式的函数类型声明:
const func: (a: number, b: string) => string = function(a, b) {
return 'hello world'
}
3.8 任意类型
any可以表示为任意类型,使用any类型可以接收所有的类型,并且可以调用其上的所有方法,在语法上不会报错。
3.9 隐式类型推断
ts可以进行隐式类型推断
let age = 18 // 隐式推断为number
age = "18" // 报错
3.10 类型断言
通过as或<>两种形式都可以进行类型断言,在JSX中<>会与标签冲突,无法使用。
在开发者比ts更明确知道变量类型的时候,可以使用类型断言。
const arr: number[] = [1,2,3,4];
const res = arr.find(item => item === 1) as number;
3.11 接口Interface
通过interface关键词可以声明接口类型,可以利用接口声明自定义的数据结构
interface Post {
title: string
content: string
subtitle?: string // 可选成员
readonly summary: string // 只读成员
}
function printPost (post: Post) {
console.log(post.title);
console.log(post.content);
}
动态成员
interface Cache {
[prop: string]: string //键值都是string
}
3.12 类class
ts中的类对ES6的类做了增强
基础使用
class Person {
name: string; // 类的成员必须先声明
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello(friend: string) {
console.log('hello ', friend);
}
}
访问修饰符
类的所有成员都可以添加访问修饰符:public(默认),private:私有,protected:受保护,子类可以访问,实例不能直接访问
class Person {
public name: string;
private age: number;
protected gender: boolean;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.gender = true;
}
sayHello(friend: string) {
console.log('hello ', friend);
}
}
class Student extends Person{
constructor(name: string, age: number) {
super(name, age);
console.log(this.gender);
}
}
const tom = new Person('tom', 18);
console.log(tom.name); // tom
// console.log(tom.age); // 报错
// console.log(tom.gender); // 报错
constructor默认为public,如果被标记为private,就不能再外部通过new的方式实例化对象,也无法被继承,可以通过静态方法,在静态方法中new出新的实例并返回。
constructor如果被标记为protected,也不能再外部new出新实例,但是可以继承。
只读属性
通过readonly关键词可以将类属性设置为只读属性。需要在声明时进行初始化,或者在构造函数中进行初始化,一旦初始化,就不能再更改。
类与接口
类可以通过implements实现(多个)接口
interface Eat {
eat (food: string): void;
}
interface Run {
run (distance: number): void;
}
class Person implements Eat, Run {
eat(food: string): void {
console.log('人吃东西:', food);
}
run(distance: number):void {
console.log('直立行走:', distance);
}
}
class Animal implements Eat, Run {
eat(food: string): void {
console.log('动物吃东西:', food);
}
run(distance: number):void {
console.log('爬行:', distance);
}
}
抽象类
通过添加abstract关键词可以使一个类变为抽象类,抽象类只能被继承,不能被实例化。抽象类中也可以定义抽象方法,也需要用abstract修饰。
抽象类可以包含某些方法的实现,接口只有成员的抽象,没有实现。
abstract class Animal {
eat(food: string): void {
console.log('动物吃东西:', food);
}
abstract run(distance: number):void;
}
class Dog extends Animal {
run(distance: number):void {
console.log('爬行:', distance);
}
}
const dog = new Dog();
dog.eat('狗粮');
dog.run(100);
3.13 泛型
泛型可以用来定义动态的类型,通过传入类型确认最终的类型。
function createArray<T> (length: number, value: T): Array<T> {
const arr = Array<T>(length).fill(value);
return arr;
}
const res1: number[] = createArray<number>(3, 100);
const res2: string[] = createArray<string>(3, 'hello');
3.14 类型声明
如果使用一个没有类型模块@types/xxx的第三方模块,需要自己去写类型声明,可以使用declare进行类型声明。