类型系统
强类型与弱类型(类型安全)
强类型
语言层面限制函数的实参类型必须与形参类型相同
//TypeScript
class Main {
static void foo(int num) {
System.out.println(num)
}
public static void main(String[] args) {
Main.foo(100); //ok
Main.foo("100"); //error "100" is a string
Main.foo(Integer.parseInt("100")); //ok
}
}
弱类型
弱类型语言层面不会限制实参的类型
//JavaScript
function foo(num) {
console.log(num);
}
foo(100) //ok
foo("100") //ok
foo(parseInt("100")) //ok
区别:
- 强类型有更强的类型约束,而弱类型中几乎没有什么约束
- 强类型语言中不允许任意的隐式类型转换,而弱类型语言是允许的
静态语言与动态语言(类型检查)
静态类型语言
一个变量声明时它的类型就是明确的,声明过后它的类型就不允许再修改
动态类型语言
运行阶段才能够明确变量类型,而且变量的类型随时可以改变。也可以理解为动态类型语言中的变量没有类型,变量中存放的值是有类型的。
强类型语言优势
- 错误更早暴露
- 代码更智能,编码更准确
- 重构更牢靠
- 减少不必要的类型判断
Flow
JavaScript的类型检查工具,通过为参数添加类型注解
,去标识参数的类型。通过flow或babel能够解析类型注解,将其转化为普通JavaScript代码。
// Flow
function sum (a:number,b:number){
return a+b
}
function sum (a:number,b){
// a : number
// b : any
return a+b
}
Flow安装与使用
yarn
1、安装flow
yarn add flow-bin --dev
2、初始化flow,生成 .flowconfig 配置文件
yarn flow init
3、项目中使用
//@flow
// 为参数添加类型注解,以标识参数类型。须在文件开头以注释的方式添加@flow标记,这样flow才会对文件进行类型检查。
function sum(a:number, b:number) {
return a + b;
}
sum(100,100)
sum('100','100')
4、运行flow&&关闭flow
yarn flow
yarn flow stop
5、通过编译移除类型注解(类型注解非JavaScript标准语法,无法正常运行)
-
flow-remove-types
安装
yarn add flow-remove-types --dev
使用 yarn flow-remove-types <源代码目录> -d <输出目录>
yarn flow-remove-types . -d dist
-
babel
安装
//安装babel preset-flow yarn add --dev @babel/core @babel/cli @babel/preset-flow
配置
//将flow添加到Babel预设配置中(在根目录创建.babelrc文件,添加如下设置) { "presets": ["@babel/preset-flow"] }
使用 yarn babel <源代码目录> -d <输出目录>
yarn babel src -d -dist
npm
1、安装flow
npm init -y
npm i flow-bin -D
2、在package.json中添加执行指令
"scripts": {
"flow": "flow",
},
3、初始化flow,生成 .flowconfig 配置文件
npm run flow init
4、项目中使用
//@flow
// 为参数添加类型注解,以标识参数类型。须在文件开头以注释的方式添加@flow标记,这样flow才会对文件进行类型检查。
function sum(a:number, b:number) {
return a + b;
}
sum(100,100)
sum('100','100')
5、运行flow&&关闭flow
npm run flow
npm run flow stop
6、通过编译移除类型注解(类型注解非JavaScript标准语法,无法正常运行)
-
flow-remove-types
安装
npm install --save-dev flow-remove-types
配置 flow-remove-types <源代码目录> -d <输出目录>
//package.json中修改配置 # 把目录文件编译之后转到dist目录下
“scripts”: {
“flow”: “flow”,
“flowRemove”: “flow-remove-types src -d dist/”
}
**使用**
npm run flowRemove
2. babel
**安装**
//安装babel preset-flow
npm install --save-dev @babel/core @babel/cli @babel/preset-flow
**配置**
//Babel配置
//将flow添加到Babel预设配置中(在根目录创建.babelrc文件,添加如下设置)
{
“presets”: [“@babel/preset-flow”]
}
//package.json文件配置
“scripts”: {
“build”: “babel ./src -d ./dist”
}
**使用**
npm run build
###### 开发工具插件
**Flow Language Support**
修改后再次保存才会校验,会有延迟
##### Flow基本使用
###### 类型推断
/**
- 类型推断
- @flow
/
function square(num){
return numnum;
}
// square(‘10’) //报错,自动推断num应为number
###### 类型注解
/**
- 类型注解
- @flow
/
// 参数类型注解
function square(num:number){
return numnum;
}
// 变量类型注解
let num:number=123
// num=‘123’
// 函数返回值类型注解
function foo():number{
// return ‘abc’
return 123
}
###### 原始类型
/**
- 原始类型
- @flow
*/
const a: string = ‘foobar’
const b: number = Infinity //NaN //100
const c: boolean = true //false
const d: null = null
const e: void = undefined
const f: symbol = Symbol()
###### 数组类型
/**
- 数组类型
- @flow
*/
const arr1: Array < number >= [1, 2, 3] //数字数组
const arr2: number[] = [1, 2, 3] //数字数组
//元组 固定长度的数组
//两个元素的数组,第一个元素为string类型,第二个元素为number类型
const arr3: [string, number] = [‘str’, 2]
###### 对象类型
/**
- 对象类型
- @flow
*/
// foo为string类型, bar为number类型
const obj1: { foo: string, bar: number } = { foo: ‘abc’, bar: 123 }
// foo设为可选
const obj2: { foo?: string, bar: number } = { bar: 123 }
// 键为string类型,值为number类型
const obj3: { [string]: number } = {}
obj3.key1=123
obj3.key2=456
###### 函数类型
/**
- 函数类型
- @flow
*/
// 指定callback为函数类型,且第一个参数为string类型,第二个参数为number类型,返回值为boolean类型
function foo(callback: (string, number) => boolean) {
callback(‘str’, 100)
}
foo(function (str, n) {
// str => string
// n => number
return n > 0
})
###### 特殊类型
/**
- 特殊类型
- @flow
*/
// a中只能存放’foo’字符串
const a: ‘foo’ = ‘foo’
// type中只能存放’success’/‘warning’/'danger’其中一个
const type: ‘success’ | ‘warning’ | ‘danger’ = ‘success’
// b为string或number类型
const b: string | number = “100”
// 使用type给一个类型组合取别名
type StringOrNumber = string | number
const c: StringOrNumber = 100
// maybe类型 gender为number或undefined或null
const gender: ? number = undefined
// 相当于
const gender1: number | null | void = undefined
###### Mixed&&Any类型
/**
- Mixed Any
- @flow
*/
// 强类型,类型安全 value为一个具体的类型
// value可以为任意类型,string|number|boolean|…
function passMixed(value: mixed) {
if (typeof value === ‘string’) {
value.substr(1)
}
if (typeof value === ‘number’) {
value * value
}
// value.substr(1) //直接使用会报错
// value * value
}
passMixed(123)
passMixed(‘string’)
// 弱类型
// value可以为任意类型,string|number|boolean|…
function passAny(value: any) {
value.substr(1)
value * value
}
passAny(123)
passAny(‘string’)
### TypeScript
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。
TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
#### 安装配置与使用
##### 安装
npm install -g typescript
##### 编译单个文件
将单个TS文件编译成JS文件
tsc greeter.ts
##### 配置文件
使用如下命令,将会在项目根目录下生成tsconfig.json文件
tsc --init
**常用选项:**
- target
指定编译后的JavaScript版本(ES5、es2015、es2016...)
“target”:“es2016”
- module
指定编译后模块化的语法。可选择 none、commonjs、amd、system、umd、es2015或esnext
“module”:“commonjs”
- rootDir
指定源代码目录
“rootDir”:“src”
- outDir
指定编译后生成文件的输出目录
“outDir”:“dist”
- sourceMap
是否为生成的JS文件创建源映射文件
此文件允许调试器和其他工具在实际使用发出的JavaScript文件时显示原始的TypeScript源代码。此文件为 .js.map (or .jsx.map) 格式,位于相应的.js 输出文件相同目录
“sourceMap”:true
- strict
是否开启严格模式
“strict”:true
##### 使用配置文件
将整个项目下的TS文件,编译为JS文件
//直接在项目根路径使用tsc命令
tsc
#### TS数据类型
##### 原始数据类型
/**
- 原始数据类型
- 与Flow中的差异:
-
非严格模式下,变量可以为空(null undefined)
*/
let a:string=‘abc’ //string
// 非严格模式下(“strict”: false),a可以为null或undefined
a=null
a=undefined
let b:number=123 //number Infinity NaN
let c:boolean=true //boolean
let d:null=null //null
let e:void=undefined //undefined
let f:symbol=Symbol() //ES6新增特性,ES6以下版本(“target”:“es5及以下”)需在标准库中添加对应声明文件(“lib”:[“es2015”])
let g:undefined=undefined //undefined
##### 标准库声明
标准库就是内置对象(Symbol、Promise、BOM、DOM...)所对应的声明
“lib”:[“ES2015”,“DOM”]
##### Object类型
/**
- object类型
*/
// 可以接收函数、数组、对象等其他非原始类型
const foo: object = function () { }//[]//{}
// 只接收对象,字面量方式,左右结构必须一致
const obj: { foo: string } = { foo: ‘123’ }
export { }
##### 数组类型
/**
- array类型
*/
const arr1: Array = [1, 2, 3]
const arr2: string[] = [‘1’, ‘2’, ‘3’]
function sum(…args: number[]) {
// 将参数定义为数字数组,累加前不用再判断传入参数是否为数字
return args.reduce((prev, cur) => prev + cur, 0)
}
sum(1,2,3)
##### 元组类型
明确元素数量和每个元素类型的数组
/**
- 元组类型(Tuple)
*/
// 明确元素个数和类型
const tuple: [number, string] = [123, ‘abc’]
export {}
##### 枚举类型
/**
- 枚举类型(Enum)
/
// 模拟枚举类型
/ const PostStatus = {
Draft: 0,
Unpublished: 1,
Published: 2
} */
//声明一个枚举类型
/* enum PostStatus {
Draft = 0,
Unpublished = 1,
Published = 2,
} */
//不给枚举类型赋值,默认从0开始累加,若给某个成员赋值,下一个成员将在其基础上累加
/* enum PostStatus {
Draft, //0
Unpublished=3, //3
Published, //4
} */
//若枚举成员值为字符串,则需要给每个成员赋值,无法累加
/* enum PostStatus {
Draft = ‘aaa’,
Unpublished = ‘bbb’,
Published = ‘ccc’,
} */
// 枚举类型会入侵代码,编译过后会生成一个双向的键值对对象,可通过值去获取键,也能通过键获取值
/* PostStatus[‘Draft’] //0
PostStatus[0] //Draft */
// 若无需通过索引器去访问枚举类型,可使用常量枚举,常量枚举编译过后会被移除
const enum PostStatus {
Draft,
Unpublished,
Published
}
// status的值为 0 草稿 1 未发布 2 已发布
const post = {
title: ‘Hello TypeScript’,
content: ‘TypeScript is a typed superset of JavaScript’,
// status: 0,//1 //2
status: PostStatus.Draft
}
export { }
##### 函数类型
/**
- 函数类型
*/
// 为函数参数和返回值指定数据类型
function fun1(a: number, b: string): void {
b = ‘QvQ’ + a;
}
fun1(100, ‘abc’) //参数个数和数据类型必须与定义的一致
// fun1(100) //报错
// fun1(‘abc’) //报错
//若希望将参数设置为可选参数,可使用 可选运算符? 或 使用参数默认值 的方式
function fun2(a?: number, b: string = ‘aaa’, …args: number[]): void {
b = ‘QvQ’ + a;
}
fun2()
// 使用函数表达式的方式声明函数
//func 接收一个函数类型的值,该函数第一个参数为number类型,第二个参数为string类型,且函数返回值为string类型
const func: (a: number, b: string) => string = function (a: number, b: string): string {
return a + ‘-’ + b
}
export { }
##### 任意类型
/**
- any任意类型(弱类型)
- any类型是不安全的
*/
// value参数可以接收任意类型的值
function stringify(value: any) {
return JSON.stringify(value)
}
stringify(123)
stringify(‘abc’)
stringify(true)
// any类型为动态类型,可以接收任意类型的值
let foo: any = 100
foo = ‘abc’
foo = false
export { }
##### 隐式类型推断
/**
- 隐式类型推断
*/
let num = 18 //此处num被推断为number类型
// num = ‘abc’ //赋值string类型的值将会报错
let foo //未指定类型且未赋值,将会推断为any类型,此时foo可以接收任意类型的值
foo = 123
foo = ‘abc’
// 为方便理解代码,建议为每个变量添加明确的类型
##### 类型断言
/**
- 类型断言
/
const nums = [1, 2, 3, 4]
const res = nums.find(item => item > 0) //此时res类型为number|undefined
// const square=resres //此时因为res类型可能为undefined类型,无法进行运算,所以会报错
let res1 = res as number //使用as关键字将res类型断言为number类型,类型断言不是类型转换,只是在编译过程将其明确为某种类型
let res2 = res //使用尖括号的方式断言,JSX下会与其语法冲突,不能使用
const square1 = res1 * res1 //不会报错
const square2 = res2 * res2 //不会报错
export { }
#### TS接口
###### 接口(interface)
用来约束对象的结构,一个对象去实现一个接口,就必须拥有这个接口所定义的所有成员。
/**
- 接口
- 用来约束对象的结构
*/
// 定义一个接口
interface News {
title: string
content: string
}
// news参数必须拥有title和content属性,此时可以定义一个接口
function showNews(news: News) {
console.log(news.title);
console.log(news.content);
}
showNews({
title: ‘Good Night!’,
content: ‘Bye~’
})
export{}
###### 可选、只读、动态成员接口
/**
- 接口
- 可选成员、只读成员、动态成员
*/
// 定义一个接口
interface News {
title: string
content: string
subtitle?: string //可选成员
readonly summary: string //只读成员 实现接口后summary值不可再修改
}
const news1: News = {
title: ‘Good Night!’,
content: ‘Bye~’,
summary: ‘2022’
}
// news1.summary=‘hello’ //只读属性,无法修改,报错
// 动态成员接口 不明确具体属性
interface DynamicInt {
[key: string]: number
}
const dynamic1: DynamicInt = {
width: 12,
height: 20,
cycle: 64
}
export { }
#### TS类
用来描述一类具体对象的抽象成员。ES6以前,使用函数+原型模拟实现类,ES6开始JavaScript中有了专门的class。TypeScript增强了class的相关语法
##### 基本使用
/**
-
类的基本使用
*/
class Person {
name: string //= ‘init name’
age: numberconstructor(name: string, age: number) {
//TS中类的属性必须先定义,不能直接在构造函数中添加(为了给属性指定类型)
//属性必须在定义时赋初始值或在构造函数中初始化
this.name = name
this.age = age
}say(msg: string): void {
console.log(${msg},${this.name}
);
}
}
export { }
##### 类的访问修饰符
/**
-
类的访问修饰符 默认为public
-
public 公有的 都能访问
-
private 私有的 只有类内部能够访问
-
protected 受保护的 只有类的内部和子类中可以访问
*/
class Person {
public name: string
private age: number
protected gender: booleanconstructor(name: string, age: number, gender: boolean) {
this.name = name
this.age = age
this.gender = gender
}say(msg: string): void {
console.log(${msg},${this.name}
);
}
}
const p1 = new Person(‘xiaoming’, 23, true)
console.log(p1.name); //可以访问
// console.log(p1.age); //私有属性,外部不能访问
// console.log(p1.gender); //受保护的属性,只能在类“Person”及其子类中访问。
class Student extends Person {
public stuNumber: string
// 构造函数也可以添加访问修饰符
private constructor(name: string, age: number, gender: boolean, stuNumber: string) {
super(name, age, gender)
this.stuNumber = stuNumber
// console.log(this.age); //子类中不能访问父类中的private属性
console.log(this.gender); //子类中可以访问父类中的protected属性
}
static createStu(name: string, age: number, gender: boolean, stuNumber: string): Student {
return new Student(name, age, gender, stuNumber)
}
}
const s1 = Student.createStu(‘xiaowang’, 20, false, ‘20230101’)
export { }
##### 类的只读属性
/**
- 类的只读属性 readonly
- 只读属性,在初始化后不允许再次修改
- 若存在访问修饰符,readonly应放在访问修饰符后面
*/
class Person {
public name: string
public readonly sex: string
constructor(name: string, sex: string) {
this.name = name
this.sex = sex
}
}
const p1=new Person(‘xigua’,‘M’)
p1.name=‘xiaoming’
// p1.sex=‘F’ //只读属性,初始化后不允许再次修改
export {}
##### 类与接口
使用interface定义接口,使用implements实现接口,一个类可以实现多个接口,接口名之间以逗号分隔。
/**
- 类与接口
*/
// 定义接口
interface EatAndSay {
eat(food: string): void
say(word: string): void
}
interface Eat {
eat(food: string): void
}
interface Say {
say(word: string): void
}
// 使用implements实现接口
class Person implements EatAndSay {
eat(food: string) {
console.log(‘用餐:’, food);
}
say(word: string) {
console.log(‘hello’, word);
}
}
// 实现多个接口,用逗号分隔
class Animal implements Eat, Say {
eat(food: string) {
console.log(‘进食:’, food);
}
say(word: string) {
console.log(‘汪汪汪?’, word);
}
}
export { }
##### 抽象类
使用abstract关键字声明当前类为抽象类,抽象类不能使用new生成实例,只能用来继承。抽象类中可以实现具体的方法继承给子类,也可以使用abstract关键字声明抽象方法等待子类实现。
/**
- 抽象类
*/
// 声明一个抽象类
abstract class Animal {
eat(food: string) {
console.log(‘进食:’, food);
}
// 声明一个抽象方法
abstract run(distance: number): void
}
// 继承一个抽象类
class Dog extends Animal {
run(distance: number): void {
console.log(‘爬行:’, distance);
}
}
const dog1 = new Dog()
dog1.eat(‘鸡腿’)
dog1.run(4)
// ========================
// 定义一个接口
interface Run {
run(speed: number): void
}
// 声明一个抽象类
abstract class Worker {
sleep(time: Object): void {
console.log(‘sleeping…’);
}
abstract work(skill: string): string[]
}
// 尝试new一个抽象类
// const w1=new Worker() //无法创建抽象类的实例,报错
// 继承一个抽象类并实现一个接口
class Programmer extends Worker implements Run {
constructor() {
super()
}
work(skill: string): string[] {
return Array(5).fill(skill)
}
run(speed: number) {
console.log(‘run’, speed);
}
}
const p1 = new Programmer()
p1.work(‘CV’)
p1.sleep(8)
export { }
#### 泛型
通过在函数名后使用<T>声明一个泛型T,代表传入参数的类型,后续传入的参数为什么类型,T就为什么类型
/**
- 泛型
*/
// 实现一个能生成数字数组的函数
function createNumberArr(length: number, value: number): number[] {
const arr = Array(length).fill(value)
return arr
}
// 实现一个能生成字符串数组的函数
function createStringArr(length: number, value: string): string[] {
const arr = Array(length).fill(value)
return arr
}
console.log(createNumberArr(5, 333));
console.log(createStringArr(5, ‘abc’));
// 使用泛型实现一个能够创建任意类型数组的函数 使用声明一个泛型T,指代后续传入的类型
function createArr(length: number, value: T): T[] {
const arr = Array(length).fill(value)
return arr
}
console.log(createArr(5, 333));
console.log(createArr(5, ‘abc’));
export { }
#### 类型声明
使用declare关键字可以为引入的第三方库,提供类型声明
/**
- 类型声明
*/
import { camelCase } from ‘lodash’
// 进行类型声明
// declare function camelCase(str: string): string
const str = camelCase(‘hello typescript’)
export { }