文章目录

  • 强类型与弱类型,静态类型与动态类型
  • Flow静态类型检查方案
  • 什么是flow?安装使用flow
  • 各数据类型类型注解
  • TypeScript语言规范与基本应用
  • ts安装使用
  • ts支持的原始类型
  • ts中的作用域
  • ts中的数据类型


强类型与弱类型,静态类型与动态类型

  • 强类型语言: 总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了
  • 弱类型语言:与强类型定义语言相反, 弱类型的语言的东西没有明显的类型,他能随着环境的不同,自动变换类型
  • 一般来说:强类型偏向于不容忍隐式类型转换,弱类型相对于强类型来说类型检查更不严格,比如说允许变量类型的隐式转换,允许强制类型转换等等。强类型语言一般不允许这么做

  • 静态类型语言:一种在编译时,数据类型是固定的语言。大多数静态类型定义语言强制这一点,它要求你在使用所有变量之前要声明它们的数据类型。
  • 动态类型语言:一种在执行期间才去发现数据类型的语言

  • 弱类型、动态语言的缺陷
    1. 动态语言由于是在执行时,才会发现数据类型,因此程序中的异常在运行时才能发现
    2. 类型不明确函数功能会发生改变(例如:写了一个sum函数,传入数字与传入字母是不同的效果)
  • 强类型的优势
    1. 错误更早暴露
    2. 代码更智能,编码更准确
    3. 减少不必要的类型判断

Flow静态类型检查方案

什么是flow?安装使用flow

一、FlowJavaScript类型检查器

  • 如下,是一个sum函数,我们需要传入的参数都为数字,这样才可以实现加和运算,写法如下:
// a:number 为 类型注解
function sum (a: number, b: number) {
  return a + b
}
console.log(sum(1, 2))

二、那么应该如果来做类型检查呢?

  1. flow-bin的安装:yarn add flow-bin
  2. 初始化.flowconfigflow init
  3. 对需要进行类型检查的文件在首行添加// @flow注解
  4. 通过yarn flow进行静态类型检查
// @flow
// a:number 为 类型注解
function sum (a: number, b: number) {
  return a + b
}
sum(1, 2);
sum('1', '2')
  • 上面可以看到,如果做类型检查,每次都要执行命令,如果希望在开发过程中就可以发现这些错误,那么可以借助一些插件来实现,在VsCode中可以安装Flow Language Support来进行类型检查

三、注解移除

  • 此时,如果需要通过node命令来执行该js文件,会发现当前文件并不能正常执行,这是由于我们在js文件中添加的类型注解,此时,如果当项目开发完毕,需要将类型注解进行移除
  1. 首先安装flow-remove-types模块:yarn add flow-remove-types --dev
  2. 执行移除命令yarn flow-remove-types src -d dist,此时src内文件中的类型注解便会移除并将文件放入dist文件夹
  • 一般编译js是通过babel来进行编译,现在我们同样通过babelbabel的一个插件来实现js代码的编译,并对类型注解进行删除
  1. 安装相关模块:yarn add @babel/core @babel/cli @babel/preset-flow --dev
  2. 在项目中添加.babelrc文件,{ "presets": ["@babel/preset-flow"]}
  3. 执行yarn babel src -d dist,此时src内文件便会进行编译,并将编译的文件放入dist文件夹

各数据类型类型注解

原始数据类型

const a: string = 'fooBar';
const b: number = Infinity;
const c: boolean = false;
const d: null = null;
const e: void = undefined;
const f: symbol = Symbol();

数组,对象类型注解`

  • 数组类型注解
const arr: Array<number> = [1,2,3,4];
const arr2: number[] = [1,2,3];
// 元组
const foo: [string, number] = ['foo', 100];
  • 对象类型注解
const ob1: {foo: string, bar: number} = {
  foo: 'string',
  bar: 100
}
// 此时可以没有bar这个属性名
const ob2: {foo: string, bar?: number} = {
  foo: 'string'
}
// obj3的属性值与属性名都必须为字符串类型
const obj3: {[string]: string} = {};
obj3.key1 = 'key1';
obj3.key2 = 'key2';
  • 函数类型注解
function fn(callback:(string, number) => void){
  callback('string', 100);
}
fn(function(str, n){
  // str => string
  // n => number
})

联合类型,mixed, Any

  • 联合类型, 可以为定义的其中一个值或者数据类型
const type: 'success' | 'warning' | 'danger'  = 'success';

type StringOrNumber = string | number;
const b: StringOrNumber = 'string';  // 100

// 在number类型的基础上扩展了null与undefined
const gender: ?number = null; // undefined;
// 类似于 const gender : number | void | null = null;
  • mixedanymixed是强类型,any是弱类型,为了兼容老代码,是不安全的,尽量不用any
// 可以接收任一数据类型, 为强类型
function passMixed(value: mixed){

}

passMixed('string');
passMixed(100);

// 可以接收任一数据类型,为弱类型
function passAny(value: any){

}
passAny('string');
passAny(100);

更多数据类型可参考:Flow Type Cheat Sheet

TypeScript语言规范与基本应用

ts安装使用

  1. yarn add typescript --dev
  2. 创建一个ts文件,代码如下:
const sayName = (name: string) => {
  console.log(`hello ${name}`); 
}

sayName('TypeScript')
  1. 执行yarn tsc start.ts,会在同目录下生成一个start.js,代码如下:
var sayName = function (name) {
    console.log("hello " + name);
};
sayName('TypeScript');
  1. 同样,也可以通过yarn tsc --init初始化一个tsconfig.json的配置文件,通过yarn tscts文件转为js文件
  2. tsconfig.json文件中一些配置项
{
  "compilerOptions": {
    "target": "es5",          // 将ts文件转为es5格式            
    "module": "commonjs",                  
    "lib": ["ES2015","DOM"],    // 指定定义的库文件,这里识别DOM,BOM及ES2015       
    "outDir": "./dist",         // 转换后文件的输出目录
    "rootDir": "./src",         // 需要转义哪个文件夹下的ts文件             
    "strict": true,             // 是否在用严格模式             
    "esModuleInterop": true,                 
    "skipLibCheck": true,                    
    "forceConsistentCasingInFileNames": true 
  }
}
  1. ts中文错误提示命令yarn tsc --locale zh-CN,如果不通过命令,可以在settings中搜索typescript local,将TypeScript Local更改为zh-CN,不过一般不建议更改,因为有些错误,需要通过网上查阅,而英文更有利于问题的解决

ts支持的原始类型

const a: string = 'foobar';
const b: number = 100;
const c: boolean = false;
// 在严格模式下,不可以对string,number, boolean类型的变量赋值为null或undefined
//const d: number = null;

const e: void = undefined // 函数没有返回值时的返回值类型
const f: null = null
const g: undefined = undefined
const h: symbol = Symbol()

ts中的作用域

  • 对于ts文件而言,每个文件都是全局作用域,这样就会导致一个问题,当两个文件中有相同的两个变量名,就会导致报错,这里提供两种解决方案:
  1. 使用自执行函数,形成局部作用域
(function(){
	  const a: number = 2;
	})()
  1. 使用export,这样每一个文件就是一个模块,而模块与模块之间是有各自的作用域的
const a: number = 2;
	export {}

ts中的数据类型

1. object类型

  • object类型可以存放非原始数据类型的数据,例如 对象,数组,函数
export {} // 确保跟其他实例没有成员冲突

const foo: object = function () {} // [] // {} 

// 使用下面方法进行定义的话,对象的赋值必须与定义的属性保持一致,不能多也不能少
// 但是一般不通过下面方式定义,可以采用接口的方式进行对象的赋值
const obj: {foo: number, bar: string} = {foo: 123, bar: 'string'}

2. 数组类型

  • 可以通过Array<number>number[]来定义一个只有数字的数组
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]

// 通过对类型的注解,这里使用sum时,可以保证传入的必须为数字
//否则就会进行报错,即可以简省类型判断的操作
function sum (...args: number[]) {
  return args.reduce((prev, current) => prev + current, 0)
}
sum(1, 2, 3)

3. 数组类型

  • 元组类型就是明确元素数量及每个元素类型的一个数组
const tuple: [number, string] = [19, 'jal']
// 下标取值
// const age = tuple[0]
// const name = tuple[1]

// 数组解构
const [age, name] = tuple

4. 枚举类型

  • 给一组数值会起一定数量便于理解的变量名
  • 给定的值是固定的几个,不会有超出可能性的值存在
  • 如下:一般如果定义一个状态,会采取以下方式
const PostStatus = {
  Draft: 0,
  Uppublished: 1,
  Published: 2 
};
const post = {
  title: 'Hello TypeScript',
  content: 'Content',
  status: PostStatus.Draft
}
  • 下面通过枚举来实现
// 通过以下枚举,在编译ts文件,会对PostStatus生成一个属性值和属性名一一对应的对象,且post中PostStatus.Draft不会被转换为对应值
// enum PostStatus {
//   Draft = 0, 
//   Uppublished = 1,
//   Published = 2
// }
// 如果通过const来定义,那么在编译ts文件后,会将post中PostStatus.Draft转换为对应的值,
const enum PostStatus {
  Draft = 0, 
  Uppublished = 1,
  Published = 2
}

const post = {
  title: 'Hello TypeScript',
  content: 'Content',
  status: PostStatus.Draft
}

5. 函数类型

// 给参数b设置默认值
// function fun(a: number, b: number = 10, ...rest: number[]): string{
function fun(a: number, b?: number, ...rest: number[]): string{
  return 'fun';
}
fun(100, 200);
fun(100);
fun(100, 200, 300)

const fun2 = function (a: number, b: number): string{
  return 'fun2'
}

6. 任意类型

  • any类型是属于动态类型
  • 任意类型不会有类型检查,所以会存在类型安全的问题,一般用于老代码兼容
function stringify(value: any){
  return JSON.stringify(value)
}
stringify('string');
stringify(100);
stringify(true);

let foo: any = 'string';
foo = 100;

7. 隐式类型推断

  • 建议给每一个变量添加类型
// 这里默认给age添加number类型
let age = 18;
// age = 'string';  // 由于进行了隐式类型推断,这里就不可以在对age赋值string类型的值


let foo;  // 不对foo赋值,那么默认foo的数据类型为any
foo = 100;
foo = 'foo';

8. 类型断言

  • 类型断言是告诉编辑器是哪种类型的方式, 与类型转换有点类似,但只用于编译时期
const nums = [110, 120, 119, 112]

const res = nums.find(i => i>0)
// 获取到的res类型:   const res: number | undefined

const num1 = res as number // 断言 res 是number
const square = num1 * num1
const num2 = <number>res // 或者这种方式。JSX下不能使用

9. 接口

  • 接口主要用来约束对象的结构
  • 可以用来约定一个对象有哪些成员,并且这些成员具体是什么样的
  • 如下约定了一个post接口
interface post {
  title: string
  content: string,
  subtitle?: string,   // subtitle为可选属性
  readonly summary: string   // summary为一个只读属性
}

function printPost(post: post){
  console.log(post.title);
  console.log(post.content);
}

printPost({
  title: 'Hello TypeScript',
  content: 'A javascript superset',
  summary: 'summary'
})
  • 动态成员接口的定义
// 动态成员
interface Cache {
  [key: string]: string  // 需要属性名为string类型
}
const cache: Cache = {};
cache.foo = 'foo';

10. 类

  • 类的基本使用
class Person {
  // 类的属性在使用之前,必须在类型当中进行声明 
  // 如果没有声明,则会报错:Property 'xxx' does not exist on type 'Person'.
  name: string = 'init Name'  
  age: number

  constructor(name: string, age: number){
    this.name = name;
    this.age = age;
  }

  sayHi(msg: string): void{
    console.log(`I am ${this.name}, ${msg}`);
  }
}
  • 类的访问修饰符
  • 访问修饰符有publicprivateprotected, 默认是public
  • private说明属性只能在类的内部被使用,在实例上是不允许的,否则会报错Property 'age' is private and only accessible within class 'Person'.
  • protected也是不被外界所引用的,与private的区别在于protected的属性在子类中可以进行访问的,如果被非子类所引用,则会报错Property 'gender' is protected and only accessible within class 'Person' and its subclasses.
class Person {
  public name: string = 'init Name'  
  private age: number
  protected 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);
    
  }
}
class Student extends Person{
  constructor(name: string, age: number){
    super(name, age)
    console.log(this.gender);
    
  }
}


const tom = new Person('tom', 8);
console.log(tom.name);
// console.log(tom.age); // Property 'age' is private and only accessible within class 'Person'.
// console.log(tom.gender); // Property 'gender' is protected and only accessible within class 'Person' and its subclasses.
  • 只读属性
  • 只读属性要么在声明的时候进行初始化,要么在构造函数中进行初始化,且只能在这两个地方进行初始化,不能修改
protected readonly gender: boolean
  • 类与接口
  • PersonAnimal都具有eatrun的属性,那么可以定义两个接口,让其对应的类实现接口
  • 为让接口更为简单,一般而言,一个接口只约束一个能力,一个类实现多个接口
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){
    console.log(`直立行走:${distance}`);
  }
}
class Animal implements Eat, Run {
  eat(food: string): void{
    console.log(`粗鲁的进餐:${food}`);
  }
  run(distance: number){
    console.log(`爬行:${distance}`);
  }
}

11. 抽象类

  • abstract修饰,不能被new,只能被继承。继承抽象类的子类,必须实现父类的抽象方法
abstract class Animal {
  eat (food: string) : void {
    console.log(`呼噜呼噜的吃:${food}`)
  }

  // 抽象方法不需要方法体,子类必须要实现抽象方法
  abstract run(distance: number): void
}

// 非抽象类“Dog”不会实现继承自“Animal”类的抽象成员“run”
class Dog extends Animal {
  run(distance: number): void {
    console.log(`四脚爬行:${distance}`)
  }
}

const dog = new Dog()
dog.run(20)
dog.eat('fish')

12. 泛型

  • 在定义函数,接口或类的时候,没有指定具体的类型,等到使用的时候再去指定具体类型的一个特征,这样可以极大程度的进行代码复用
  • 以函数为例,在定义的时候不指定具体的类型,在进行函数调用的时候,指定具体的类型,下面实现了一个创建数组的方法
function createNumberArray(length: number, value: number): number[] {
  const arr = Array<number>(length).fill(value)
  return arr
}

const res = createNumberArray(3, 100 ) // [100, 100, 100]

function createArray<T> (length: Number, value: T): T[] {
  const arr = Array<T>(length).fill(value)
}

const arrRes = createArray<string>(3, 'foo') // ['foo', 'foo', 'foo']

13. 类型声明

  • 一个成员在定义的时候因为某些原因没有声明一个明确的类型,在使用的时候可以单独为它做出一个明确的说明,主要为了考虑兼容一些js模块
  • 类型声明通过declare来进行声明,不过一般常用的模块都已经做了类型声明
import {camelCase} from 'lodash'
// 自己写declare语句声明类型
declare function camelCase (input: string): string

const res = camelCase('zjal')