下一篇:javascript的性能优化 juejin.cn/post/688348…
1. typeScript 和javascript 对比
typescript: 是强类型,静态类型;从语言层面限制函数的实参类型必须于形参类型相同,不允许隐式转换
1\. 强类型代码错误更早暴露
2\. 强类型代码更智能,编码更准确
3\. 重构更可靠,重构更可靠
4\. 减少了代码层面的不必要的类型判断
javascript: 是弱类型,动态类型;不会限制实参的类型,允许隐式转换
1\. 异常需要等到运行时才能发现
2\. 函数功能可能发生改变
3\. 对象索引器的错误用法
// const obj = {}
//obj[true] = 100 // 属性名会自动转换为字符串
//console.log(obj['true'])
// 君子约定由隐患,强制要求有保障它俩之间的区别就是是否允许类型转换
** 1.2 类型检查:静态类型vs动态类型 **
静态语言: 一个变量声明时它的类型就是明确的,声明过后,它的类型就不允许再修改
动态语言:在运行阶段才能明确变量类型,并且可以变化,变量时没有类型的,变量的值是有的
静态类型 需要在编译时做类型检查,javascript没有编译环节
2. flow -----javaScript 的类型检查工具
好处: 为javaScript提供更完善的类型系统
工作原理:通过添加类型注解的方式来标记代码当中变量或者参数应该是什么类型的,
flow根据类型注解来检查代码当中是否 类型异常,从而实现我们在开发阶段对类型的检查
// 冒号后面跟类型的用法 叫类型注解, 表示a 必须为number
function sum (a: number, b: number) {return a + b}
sum(100, 100)
// number 会报错,下面步骤是设置关闭类型注解,number就不会报错
** 2.1 安装 flow-bin + 设置编辑器**
1\. yarn add flow-bin --dev // 安装
2\. 要在文件一开始 位置通过注解的方式添加@flow 的标记,flow去执行检测时才会检 查这个文件
3\. yarn init // 会多出.flowconfig 文件
4\. yarn flow // 开始flow
5\. yarn flow stop // 结束这个命令
2.2 flow 编译移除注解 (不是javascript 标准语法,会造成代码无法运行)
解决方式:
1\. 使用 官方提供的方法
yarn flow-remove-types . -d dist
// " . " 第一个参数代表源代码所在的目录(把原来的代码放在src文件中防止误删:. 就可以写成src)
// -d 指定转换过后的输出目录设置dist
2\. 使用babel 会生成 .babelrc
2.1 yarn add @babel/core @babel/cli @babel/preset-flow --dev
// core是babel 的核心模块,
// cli是babel的工具,让我们在命令行直接去使用babel命令并完成编译
// 转化类型注解的插件
2.2 在.babelrc 里添加
{
"presets": ["@babel/preset-flow"]
}
2.3 yarn babel src -d dist // 把src下的所有文件都编译转换到dist目录中
** 2.3 flow 开发工具插件**
更直观的体现代码中的类型问题
在vscode 中安装 flow language Support
// https:// flow.org/en/docs/editors
// flow 官网
** 2.4 flow 类型推断**
/** * 类型注解 * * @flow */
function square (n) {
return n * n}// square('100') 会直接 红线报错square(100)
根据代码当中的使用情况去推断出来变量和类型,这样的特征叫类型推荐
2.5 flow 类型注解
/** * 类型注解 * * @flow */// 不仅可以用在参数上,还可以标记变量的类型function square (n: number) { return n * n}// 标记变量的类型为numberlet num: number = 100// num = 'string' // errorfunction foo (): number { // 这个函数只允许返回 number,返回其他会报错 return 100 // ok // return 'string' // error}function bar (): void { // void 可返回undefined // return undefined}
** 2.6 flow 原始类型**
/** * 原始类型 * * @flow */const a: string = 'foobar' // 只能存在字符串const b: number = Infinity // NaN // 100 // 可存放无穷大,NaN 和 数字const c: boolean = false // trueconst d: null = nullconst e: void = undefinedconst f: symbol = Symbol()
2.7 flow 数组类型
/** * 数组类型 * * @flow */// 1\. flow支持两种数组类型的表现方式 <number> 是范型参数来描述数组中的每个元素的类型const arr1: Array<number> = [1, 2, 3] // 表示 全是由数字组成的数组 // 2\. 同样表示全是由数字组成的数组const arr2: number[] = [1, 2, 3]
// 元组
// 表示固定长度的数组,用字面量的方式(需要多个返回值的时候 需要这种元组)const foo: [string, number] = ['foo', 100] // 第一必须是字符串,第二是数字
2.8 flow 对象类型
/** * 对象类型 * * @flow */// 必须得返回string 和numberconst obj1: { foo: string, bar: number } = { foo: 'string', bar: 100 }
// 在后面添加? 表示这个string 可有可无
const obj2: { foo?: string, bar: number } = { bar: 100 }// 如果要明确键和值 ,需要类似索引器得写法 { [string]: string } 必须是字符串
// const obj3 = {}
// obj3.key1 = 'value1'
// obj3.key2 = 100
const obj3: { [string]: string } = {}
obj3.key1 = 'value1'
obj3.key2 = 'value2'
** 2.9 flow 函数类型**
/** * 函数类型 * * @flow */// 用函数签名地方式 限制 callback必须有string和number ,用箭头函数返回void表示没有返回值function foo (callback: (string, number) => void) { callback('string', 100)}// 必须遵循上面 foo(function (str, n) { // str => string // n => number})
** 2.10 flow 特殊类型**
/** * 特殊类型 * * @flow */// 字面量类型: 用来限制变量必须是某一个值 (不会单独使用)const a: 'foo' = 'foo' // 必须是'foo' 如果是 'foo1' 也会报错// 必须是 success,warning,danger其中之一,其他地都会报错 (或类型)
const type: 'success' | 'warning' | 'danger' = 'success'// ------------------------// 声明类型// 可以是字符串或者数字type StringOrNumber = string | number // StringOrNumber 相当于类型地别名const b: StringOrNumber = 'string' // 100// ------------------------// Maybe 类型 (?有可能)const gender: ?number = undefined// 相当于 可以接收 number,null,undefined// const gender: number | null | void = undefined
2.11 flow Mixed 与 Any
/** * Mixed Any * * @flow */// 共同点:mixed,Any 可以接收任何类型的值
// 区别:any是弱类型,mixed 是强类型
mixed是具体的类型,如果没有明确是字符串的话,就不可以当成字符串使用
尽量不用any,any 意义主要兼容以前的老代码
// string | number | boolean | ....function passMixed (value: mixed) {
// 传入string和数字,语法报错
//value.substr(1)// value * value // mixed可以用这种判断就不会报错 if (typeof value === 'string') { value.substr(1) } if (typeof value === 'number') { value * value }}passMixed('string')passMixed(100)// ---------------------------------// 传入字符串和数字 ,语法上并不会报错function passAny (value: any) { value.substr(1) value * value}passAny('string')passAny(100)
** 2.12 flow 类型小结**
官网中对所有类型描述的文档
https:// flow.org/en/docs/types
第三方类型手册:
https://www.saltycrane.com/cheat-sheets/flow-type/latest/
2.13 flow 运行环境API
/** * 运行环境 API * * @flow */内置语法const element: HTMLElement | null = document.getElementById('app')
// https://github.com/facebook/flow/blob/master/lib/core.js
//core.js 是javascript 自身准备库
// dom.js | bom.js | cssom.js | node.js 其他的api申明
3. typescript (是javascript的超集或者是扩展集)
3.1 typescript 优点和缺点
typescript :
1\. 避免类型异常
2.支持转换es6中的新特性
3\. 最低能编译到es3版本的代码(兼容好)
4\. 任何一种javascript运行环境都支持
5\. 功能更为强大,生态也更健全,更完善 angular/vue3.0
缺点:
1\. 语言本身多了很多概念 (属于渐进式)
2\. 项目初期,typeScript 会增加一些成本 (大型项目就不会在意这些成本)
3.2 基本使用
// 安装
yarn add typescript --dev
// 安装过后node 模块中bin里会多一个tsc 的文件,他是用来编译代码的
// typescript文件的扩展名是 ' xxxx.ts '
// 可以完全按照 JavaScript 标准语法编写代码
事列代码:
const hello = (name: any) => { console.log(`Hello, ${name}`) } hello('TypeScript')1\. yarn tsc 01-fileName // 运行后会多出一个同名的js文件 ,里面试是被编译好的js文件
3.3 typescript 配置文件
// tsc 不仅可以编译单个文件,也可以编译整个项目
// 在这之前要生成配置文件
1\. yarn tsc --init // 会多出一个tsconfig.json配置文件
2.设置完 tsconfig.json
3\. yarn tsc // 把src 文件夹里面ts文件 都编译dist文件夹里
设置 tsconfig.json配置文件
3.4 typescript 原始类型
// 原始数据类型string / number/ boolean 这三种默认允许为空 // 如果配置文件中开启严格模式就会报错const a: string = 'foobar'const b: number = 100 // NaN Infinityconst c: boolean = true // false// 在非严格模式(strictNullChecks)下,// string, number, boolean 都可以为空// const d: string = null// const d: number = null// const d: boolean = nullconst e: void = undefinedconst f: null = nullconst g: undefined = undefined// Symbol 是 ES2015 标准中定义的成员,// 使用它的前提是必须确保有对应的 ES2015 标准库引用// 也就是 tsconfig.json 中的 lib 选项必须包含 ES2015, 或者target:'es2015'const h: symbol = Symbol()
// 标准库就是内置对象所对应的声明文件// Promise // 右击 + 选择转到定义
3.5 typescript 中文错误消息
// const error: string = 100 // 当error报错是英文,使用下面这个,使报错已中文显示
方法1: yarn --local zh-CN
方法2: 首选项-----设置------ 输入typescript local ---- 第一个设置为 zh-CN
3.7 typescript 作用域问题
// 作用域问题// 默认文件中的成员会作为全局成员// 多个文件中有相同成员就会出现冲突// const a = 123 // 报错// 解决办法1: IIFE 提供独立作用域// (function () {// const a = 123// })()// 解决办法2: 在当前文件使用 export,也就是把当前文件变成一个模块// 模块有单独的作用域const a = 123export {} // 这是一个语法并不是导出一个模块
3.8 typescript--Object类型
// Object 类型export {} // 确保跟其它示例没有成员冲突// object 类型是指除了原始类型以外的其它类型const foo: object = function () {} // [] // {}// 如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」const obj: { foo: number, bar: string } = { foo: 123, bar: 'string' } // 不能多也不能少
3.9 typescript 数组类型
// 数组类型export {} // 确保跟其它示例没有成员冲突// 数组类型的两种表示方式const arr1: Array<number> = [1, 2, 3] // 表示纯数字的数组const arr2: number[] = [1, 2, 3] // 同上// 案例 -----------------------// 如果是 JS,需要判断是不是每个成员都是数字function sum (...args: number[]) { // 使用 TS,类型有保障,不用添加类型判断 return args.reduce((prev, current) => prev + current, 0)
//reduce 第一个参数是上一次计算的结果, 第二参数是本次循环的当前值 ,内部第二个参数代表默认值}sum(1, 2, 3) // => 6
3.10 typescript 元组类型
// 元组(Tuple)export {} // 确保跟其它示例没有成员冲突const tuple: [number, string] = [18, 'zce'] // 只能存在两个对应的元素// const age = tuple[0]// const name = tuple[1]const [age, name] = tuple// ---------------------const entries: [string, number][] = Object.entries({ foo: 123, bar: 456})const [key, value] = entries[0]// key => foo, value => 123
3.11 typescript 枚举类型
// 枚举(Enum)1\. 他能给一组数值分别起更好理解的名字
2\. 一个枚举中只会存在一些固定的值,并不会出现超出范围的可能性
// enum 关键字
export {} // 确保跟其它示例没有成员冲突// 用对象模拟枚举// const PostStatus = {// Draft: 0,// Unpublished: 1,// Published: 2// }// 标准的数字枚举// enum PostStatus {// Draft = 0,// Unpublished = 1,// Published = 2// }// 数字枚举,枚举值自动基于前一个值自增// enum PostStatus {// Draft = 6,// Unpublished, // => 7 // 如果第一个也没有值 ,那就会从0开始,Unpublished为1// Published // => 8// }// 字符串枚举// enum PostStatus {// Draft = 'aaa',// Unpublished = 'bbb',// Published = 'ccc'// }// 常量枚举,不会侵入编译结果const enum PostStatus { // const 会不去用索引器的当时访问枚举,用更简单的方式去标注 Draft, Unpublished, Published}const post = { title: 'Hello TypeScript', content: 'TypeScript is a typed superset of JavaScript.', status: PostStatus.Draft // 3 // 1 // 0}// PostStatus[0] // => Draft
3.12 typescript 函数类型
1.函数声明
// 函数类型export {} // 确保跟其它示例没有成员冲突function func1 (a: number, b: number = 10, ...rest: number[]): string {
// string 代表返回值的类型, ...rest: number[] 接收任意个数的参数
return 'func1'}func1(100, 200)func1(100)func1(100, 200, 300)// -----------------------------------------
2\. 函数表达式 const func2: (a: number, b: number) => string = function (a: number, b: number): string { return 'func2' }
3.13 typescript 任意类型
// 任意类型(弱类型)export {} // 确保跟其它示例没有成员冲突function stringify (value: any) { // any 接收任意类型数据 return JSON.stringify(value)}stringify('string')stringify(100)stringify(true)let foo: any = 'string'foo = 100foo.bar()// any 类型是不安全的
3.14 typescript 隐式类型推断
// 隐式类型推断export {} // 确保跟其它示例没有成员冲突let age = 18 // number// age = 'string' // error: 上一步已经被推断为numberlet foo // 声明变量一开始不赋值,它就会自动当成any类型foo = 100foo = 'string'// 建议为每个变量添加明确的类型标注
3.15 typescript 类型断言
// 类型断言export {} // 确保跟其它示例没有成员冲突// 假定这个 nums 来自一个明确的接口const nums = [110, 120, 119, 112]const res = nums.find(i => i > 0) // 找出第一个大于0的值 res会自动推断number或者undefined类型// const square = res * resconst num1 = res as number // 断言:用 as 的方式 他是一个数字 (推荐)const num2 = <number>res // 断言: JSX 下不能使用
3.16 typescript 接口
// 接口: 一种规范或者契约 ,它可以约定一个对象具体应该有那些成员,并且类型是什么样的export {} // 确保跟其它示例没有成员冲突interface Post { // interface 关键词 title: string content: string}function printPost (post: Post) { console.log(post.title) console.log(post.content)}printPost({ title: 'Hello TypeScript', content: 'A javascript superset'})
3.17 typescript 接口补充
// 可选成员、只读成员、动态成员export {} // 确保跟其它示例没有成员冲突// -------------------------------------------interface Post { // 可选成员 title: string content: string subtitle?: string // ?表示可有可无 readonly summary: string // 定义readonly 让下面只读,不可以设置}const hello: Post = { // 只读成员 title: 'Hello TypeScript', content: 'A javascript superset', summary: 'A javascript'}// hello.summary = 'other' // 报错 ,原因: 定义了readonly // ----------------------------------interface Cache { //动态成员 [prop: string]: string
// prop 可以是任意属性名,第一个string是 键的类型,第二个动态属性的值是string}const cache: Cache = {}cache.foo = 'value1'cache.bar = 'value2'
3.18 typescript 类的基本使用
// 类(Class):作用:描述一类具体事物的抽象特征(不能直接使用类)
// 类可以用来描述一类具体对象的抽象成员,
// es6以前,函数+原型模拟实现类,es6后有专门的classexport {} // 确保跟其它示例没有成员冲突class Person { name: string // = 'init name' 也可以赋值初始值 age: number constructor (name: string, age: number) { // 建一个构造函数 this.name = name // 需要直接在上面定义name的类型 this.age = age } sayHi (msg: string): void { console.log(`I am ${this.name}, ${msg}`) }}
3.19 typescript 类的访问修饰符
// 类的访问修饰符export {} // 确保跟其它示例没有成员冲突1\. class Person { public name: string // = 'init name' // 默认就是public private age: number // private 设置私有属性,只能在内部访问 ,它允许继承 protected gender: boolean // protected 受保护的,外部不能访问,只允许在此类访问成员 2\. constructor (name: string, age: number) { this.name = name this.age = age this.gender = true } 3\. sayHi (msg: string): void { console.log(`I am ${this.name}, ${msg}`) console.log(this.age) }}4 . class Student extends Person { // Student 去继承Person private constructor (name: string, age: number) {
// 调用构造函数// 如果使用private 在外部就无法访问也无法继承
super(name, age) console.log(this.gender) // 只允许在此类访问成员 } 5\. static create (name: string, age: number) {
//因为 4.设置了private ,所以定义一个静态 让外面可以访问
return new Student(name, age) }}const tom = new Person('tom', 18)console.log(tom.name)// console.log(tom.age) // 标记为私有属性无法访问// console.log(tom.gender) // 访问不到,因为是受保护的const jack = Student.create('jack', 18)
3.20 typescript 类的只读属性
// 类的只读属性export {} // 确保跟其它示例没有成员冲突class Person { public name: string // = 'init name' private age: number // readonly 只读成员
// 将protected 设置成readonly protected readonly gender: boolean constructor (name: string, age: number) { this.name = name this.age = age this.gender = true } sayHi (msg: string): void { console.log(`I am ${this.name}, ${msg}`) console.log(this.age) }}const tom = new Person('tom', 18)console.log(tom.name)// tom.gender = false
3.21 typescript 类与接口
// 类与接口export {} // 确保跟其它示例没有成员冲突interface Eat { eat (food: string): void}interface Run { run (distance: number): void}1\. class Person implements Eat, Run { // 定义了Eat, Run就必须要有eat和run两个成员 eat (food: string): void { console.log(`优雅的进餐: ${food}`) } run (distance: number) { console.log(`直立行走: ${distance}`) }}2\. class Animal implements Eat, Run { // 定义了Eat, Run就必须要有eat和run两个成员 eat (food: string): void { console.log(`呼噜呼噜的吃: ${food}`) } run (distance: number) { console.log(`爬行: ${distance}`) }}
3.22 typescript 抽象类
// 抽线类:包含具体的实现export {} // 确保跟其它示例没有成员冲突abstract class Animal { // abstract 定义抽象类,只能被继承,不能使用new的方式去新建 eat (food: string): void { console.log(`吃: ${food}`) } abstract run (distance: number): void // 抽象方法}class Dog extends Animal { // 继承 run(distance: number): void { console.log('四', distance) }}const d = new Dog()d.eat('嗯')d.run(100)
3.23 typescript 泛型
// 泛型:声明这个函数时我们不去其指定具体的类型,在我们传递的时候再指定具体的类型
//目的:极大程度上复用我们的代码export {} // 确保跟其它示例没有成员冲突// 创建指定长度的数组function createNumberArray (length: number, value: number): number[] { const arr = Array<number>(length).fill(value) return arr}// 如果想创建string类型的数组,最笨的办法function createStringArray (length: number, value: string): string[] { const arr = Array<string>(length).fill(value) return arr}// 好办法:把类型定义为一个参数,定义泛型参数<T>,然后把函数中类型不明确的改成T来表示function createArray<T> (length: number, value: T): T[] { const arr = Array<T>(length).fill(value) return arr}// const res = createNumberArray(3, 100)// res => [100, 100, 100]const res = createArray<string>(3, 'foo')// 第二个可以创建任意类型参数
3.24 typescript 类型声明
// 类型声明
1\. yarn add lodash --dev
//类型声明模块 或者 yarn add query-string 用来解析url中的querystring字符串
2\. yarn add @types/lodash --dev import { camelCase } from 'lodash' // 把字符串转换为驼峰的形式import qs from 'query-string'qs.parse('?key=value&key2=value2')// declare function camelCase (input: string): string // declare 函数声明,就会有类型限制const res = camelCase('hello typed')export {} // 确保跟其它示例没有成员冲突