拉勾大前端的学习笔记,仅作为学习记录
是基于JavaScript之上的编程语言,重点解决了js类型系统里面的不足,大大提高代码的可靠程度
内容概要
- 强类型与弱类型
- 静态类型和动态类型
- JavaScript自有类型系统的问题
- Flow静态类型检查方案
- TypeScript语言规范和基本应用
类型系统
下面的类型是从两个维度来区分类型的类型安全维度和类型检查维度
- 强类型与弱类型 (类型安全)
- 静态类型与动态类型 (类型检查)
类型安全
- 强类型:在语言层面限制了函数的实参类型必须与形参类型相同
- 弱类型:在语言层面不会限制实参的类型
强类型语言中不允许有随意的数据隐式类型转换,而弱类型语言则允许的
类型检查
- 静态类型:一个变量声明时类型就是明确的,一旦被声明他的类型就不允许被修改
- 动态类型:运行阶段才可以明确一个变量的类型,变量的类型随时可以改变
JavaScript类型系统特征
弱类型 且 动态类型
弱类型的问题
- 等到运行阶段才会发现代码中的类型异常
- 函数中传入参数类型不明确,可能会造成函数功能发生改变
- 会有隐式转换
强类型的优势
- 错误更早的暴露
- 代码更智能,编码更加准确
- 重构更牢靠
- 减少代码层面不必要的判断
Flow概述——JavaScript静态类型检查器
- 工作原理:在代码证通过添加类型注解的方式来标记代码中每个变量或者参数应该是什么类型的,Flow根据类型注解检查代码中是否存在类型使用异常,从而实现在开发阶段对类型异常的检查
//
:number 代表类型注解
function sum (a: number, b:number){
return a + b
}
sum(100, 50 )
sum('100', 50) // 如果传入的不是数字 语法上就会检测出对应的异常
类型注解在生产环境中可以通过babel 或者flow中的模块去掉注解
快速上手Flow
- flow是以npm模块去工作的,通过yarn安装flow
yarn add flow-bin --dev
- 作用就是检测当前代码中的类型异常
- 使用flow的时候要在当前文件的开头加一个注释, 这样的flow会对当前代码做类型检查
// @flow
- 当vscode报错语法报错是因为 :number不是es语法的时候,当使用flow的时候需要在VSCode关闭es语法检测,打开设置项,搜索javascript validate, 关掉enable选项
- 运行命令去校验代码中的flow代码
yarn flow
// 报错not find a .flowconfig
// 这时候要yarn flow init 去进行初始化
- 第一次执行后,flow会启动一个后台命令去监视我们的文件
- 完成编码后,使用一下stop命令结束监视
yarn flow stop
Flow通过编译移除「类型注解」
- 使用官方提供的 flow-remove-types
yarn add flow-remove-types
yarn flow-remove-types src[当前目录] -d dist[输出目录]
- babel去移出类型注解
yarn add @babel/core @babel/cli @babel/preset-flow --dev
// 安装过后在项目中添加.babelrc文件
// 文件中添加presets配置
{
"presets": ['@babel/presre-flow']
}
Flow开发工具插件
在VSCode中安装插件 Flow Labguage Support
类型推断
根据代码当中的使用情况,推断出变量的类型的特征就叫做类型推断
原始类型
const a:string = "footer"
const b:number = Infinity // NaN // 100
const c:boolean = true // false
const d:null = null
const e:void = undefined
const f: symbol = Symbol()
数组类型
// Array需要泛型参数指定数组中每一项的数据类型,
// 下面两个 表示为全部由数字组成的数组
const arr1: Array<number>
const arr2: number[]
//存放指定长度的数组,而且第一个是字符串,第二个是数字,此类做法称为元组
const foo:[string,number] = ['foo',100]
对象类型
// foo? 代表foo成员为可选属性
const obj1: { foo?:string, bar:number } = {
foo: 'foo',
bar: 123
}
// 当前对象允许添加任意个数的键,不过键和值的类型都必须是string类型
const obj2: { [string]:string } = {}
函数类型
一般指函数的参数类型和函数的返回值类型进行约束
// 限制回调函数的参数和他的返回值
function foo (calback:(string,number) => void ){
callback('string',100)
}
//foo传入的回调函数需要有两个参数一个是string,一个是number,并且这个函数没有返回值
foo(function(str,n){
})
特殊类型
- 字面量类型
一般配合联合类型的方法去组合几个特定的值
// a变量的值 只能存放'foo'
const a: 'foo' = 'foo'
// type只能存 下面三个值
const type : 'success' | 'warning' | 'danger' = 'success'
- 联合类型
//代表 b 的类型 只能是string 或者是number
const b:string | number = 100
- 可以用type 关键词去声明一个类型
const StringOrNumber = string | number
const b:StringOrNumber = 100
- maybe类型
// 表示gender除了可以接收number,还可以去接收 null 或者undefined
const gender: ?number = null
Mixed && Any
// mixed 可以接收任意类型的数据
function passMixed( value:mixed ){
}
// any也可以接收任意类型的数据
function passAny( value:any){
}
//any是弱类型,mixed是强类型
TypeScript
是js的超集,包括js+类型系统+es6+ ,最终会被编译为js
- 缺点:语言本身多了很对概念,提高学习成本;项目初期,TypeScript会增加一些成本
快速上手
yarn init --yes
yarn add typescript --dev'
// 这个模块提供一个tsc命令去编译ts文件
yarn tsc 文件名 // 编译转换ts代码为js
// 执行tsc命令回先去看代码中的类型使用是否正确,然后移出类型注解,并且自动转化ES 的新特性
配置文件
yarn tsc -- init // 初始化一个tsconfig.json 文件
yarn tsc // 编译整个项目
原始数据类型
- ts中,在非严格模式下 前三个类型允许为空,可以给他赋值为 null 或undefined
const a:string = "footer"
const b:number = Infinity // NaN // 100
const c:boolean = true // false
const d:null = null
const e:void = undefined
const f: symbol = Symbol()
// 因为tsconfig中的target: "es5",所以默认使用es5作为标准库,所以使用ES6的Symbol 在语法上就会报错
// 解决办法就是在tsconfig中的lib里面加上 lib:['ES2015'],表示引用ES2015作为默认标准库
// 在lib:['ES2015','DOM'],console.log就不会报错了
- 标准库就是内置对象所对应的声明文件,想在代码中使用内置对象,就必须引用对应的标准库
作用域问题
- 不同文件中有相同变量名称的情况
// 导出模块,把文件作为单独的模块导出,可以解决相同变量名的问题,因为每个模块单独的作用域
export {}
Object类型
- ts中object类型并不单指普通的对象类型,而是泛指所有非原始类型,也就是对象,数组,函数。
- 普通对象的类型可以使用对象字面量的方式声明,单大多情况下还是使用接口去声明
const foo: object = function (){} // [] // {}
const obj: { foo:number } = { foo:number } // 对象字面量的方式声明
数组类型
- arr泛型
- 元素类型加[]的方式
const arr1: Array<T> = [1,2,3]
const arr2: number[] = [1,2,3]
元组类型
- 明确元素数量已经各个类型的数组
const tuple: [number, string] = [18, 'zce']
枚举类型
- 需要用某几个数值,表示变量的状态
enum PostStatus {
Draft = 0,
Unpublished = 1,
Published = 2
}
// 当不给枚举类型加默认值时,默认从0开始递增
const post = {
title: 'Hello',
content: 'hello,world'
status: PostStatus.Drafit //0代表未发布,1草稿,2已经发布了
}
// 枚举类型会影响编译后的结果,最终会编译为双向的键值对对象
// 当给枚举加上const 的时候,编译完成后枚举类型会被移出,代码中状态,会被换成对应的数值
函数类型
- 对函数的输入输出做约束
- 函数声明和函数表达式 分别如何约束
// 函数声明的约束
function func1(a?:number):string{
return 'hello'
}
//在参数名后面加上?代表参数为可选参数
// 函数表达式对应的类型限制
const func2:(a:number) => string = function (a:number):string{
return ''func2"
}
任意类型
- any 属于动态类型,可以用来接收任意类型的值
隐式类型推断
let age = 18 // 这个类型就会被ts推断为number类型
age = 'hello' // 再赋值字符串就会被报错
const foo // 当无法推断类型的时候,推断为any
类型断言
- 有些情况下ts无法推断出具体类型,我们可以使用断言告诉ts此变量就是**类型
- 只是编译过程中的概念,编译过后就没什么意义了
const nums = [100,12,119]
const res = nums.find(i => i >= 0) // 此时ts会推断res为number | undefined
// 断言的两种方式
const num1 = res as number
const num2 = <number>res //JSX下不能使用,有标签冲突
接口(Interfaces)
可以理解为一种规范,或者是一种契约,可以用来约定对象的接口,使用接口,就必须遵循接口全部的约定
- ts中的接口只是为有结构的数据进行类型约束的,在实际运行阶段这种接口并没有意义,不会被编译到实际运行的代码中
// 可以约定一个对象中具体应该有哪些成员,并且成员的类型都是什么样的
interface Post {
title : string
constext: string
}
function printPost(post: Post){
// 这种调用就是post有些要求,post必须要有title和context属性
// 可以用接口描述这种隐式的约束
console.log(post.title)
console.log(post.context)
}
可选成员
//subtitle为可选成员
interface Post {
title : string
constext: string
subtitle?: string
}
只读成员
//subtitle为可选成员
interface Post {
title : string
constext: string
subtitle?: string
readonly summary:string
}
// summary在初始化完成后就不允许被修改了
动态成员
interface Cache{
[key: string] : string // 表示键的类型为string,值的类型也为string,
}
const cache:Cahe = {}
cache.foo = 'foo'
cache.bar = 'bar'
类(Classes)
- 描述一类具体事物的抽象特征
- 用来描述具体对象的抽象成员
- ts中增强了类的相关语法
// 在ts中类的属性在赋值之前要去声明他的类型
class Person = {
public name: string = 'hello' // 可以通过等号的方式设置初始值
private age: number
protected readonly gender: boolean //只读属性readonly
constructor (name: string, age:number){
this.name = name
this.age = age
}
}
访问修饰符
- private 表示私有属性,只能在内部进行访问
- public 表示公有成员,默认就是public
- protected 表示受保护的,不能在外部访问,只允许在子类中访问对应的成员
类和接口
利用接口对类进行抽象
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 class Animal {
eat(food:string): void{
console.log(`呼噜呼噜的吃:${food}`)
}
abstract run (distance: number): void // 抽象类不需要具体的实现方法
}
// 使用子类继承Animal类型
class Dog extends Animal{
// 父类中有抽象方法,子类中就必须要去实现那个抽象方法
run(distance: number):void{
console.log(`四脚爬行:${distance}`)
}
}
泛型
- 定义函数接口和类的时候没有定义具体的类型,等使用的时候再去指定具体类型的特征
- 简单的说声明函数的时候不去指定具体的类型,等调用的时候再去指定这个类型
// 创建数字类型的数组
function createNumberArray(length: number, value:number): number[]{
const arr = Array<number>(length).fill(value)
return arr
}
// 创建字符串类型的数组
function createNumberArray(length: number, value:string): string[]{
const arr = Array<string>(length).fill(value)
return arr
}
// 使用泛型优化上面的函数,支持创建任意类型的数组
function createArray<T>(length: number, value:T): T[]{
const arr = Array<T>(length).fill(value)
return arr
}
类型声明
- 对第三方的模块进行类型声明
- typescript社区有维护一些第三方模块的类型声明
// 例如添加lodash的类型声明
yarn add @types/lodash --dev