文章目录
强类型与弱类型,静态类型与动态类型
- 强类型语言: 总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了
- 弱类型语言:与强类型定义语言相反, 弱类型的语言的东西没有明显的类型,他能随着环境的不同,自动变换类型
- 一般来说:强类型偏向于不容忍隐式类型转换,弱类型相对于强类型来说类型检查更不严格,比如说允许变量类型的隐式转换,允许强制类型转换等等。强类型语言一般不允许这么做
- 静态类型语言:一种在编译时,数据类型是固定的语言。大多数静态类型定义语言强制这一点,它要求你在使用所有变量之前要声明它们的数据类型。
- 动态类型语言:一种在执行期间才去发现数据类型的语言
- 弱类型、动态语言的缺陷
1. 动态语言由于是在执行时,才会发现数据类型,因此程序中的异常在运行时才能发现
2. 类型不明确函数功能会发生改变(例如:写了一个sum
函数,传入数字与传入字母是不同的效果)- 强类型的优势
1. 错误更早暴露
2. 代码更智能,编码更准确
3. 减少不必要的类型判断
Flow静态类型检查方案
什么是flow?安装使用flow
一、
Flow
是JavaScript
类型检查器
- 如下,是一个
sum
函数,我们需要传入的参数都为数字,这样才可以实现加和运算,写法如下:
// a:number 为 类型注解
function sum (a: number, b: number) {
return a + b
}
console.log(sum(1, 2))
二、那么应该如果来做类型检查呢?
flow-bin
的安装:yarn add flow-bin
- 初始化
.flowconfig
:flow init
- 对需要进行类型检查的文件在首行添加
// @flow
注解 - 通过
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
文件中添加的类型注解,此时,如果当项目开发完毕,需要将类型注解进行移除- 首先安装
flow-remove-types
模块:yarn add flow-remove-types --dev
- 执行移除命令
yarn flow-remove-types src -d dist
,此时src
内文件中的类型注解便会移除并将文件放入dist
文件夹
- 首先安装
- 一般编译
js
是通过babel
来进行编译,现在我们同样通过babel
与babel
的一个插件来实现js
代码的编译,并对类型注解进行删除- 安装相关模块:
yarn add @babel/core @babel/cli @babel/preset-flow --dev
- 在项目中添加
.babelrc
文件,{ "presets": ["@babel/preset-flow"]}
- 执行
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;
mixed
与any
:mixed
是强类型,any
是弱类型,为了兼容老代码,是不安全的,尽量不用any
// 可以接收任一数据类型, 为强类型
function passMixed(value: mixed){
}
passMixed('string');
passMixed(100);
// 可以接收任一数据类型,为弱类型
function passAny(value: any){
}
passAny('string');
passAny(100);
更多数据类型可参考:Flow Type Cheat Sheet
TypeScript语言规范与基本应用
ts安装使用
-
yarn add typescript --dev
-
创建一个
ts
文件,代码如下:const sayName = (name: string) => { console.log(`hello ${name}`); } sayName('TypeScript')
-
执行
yarn tsc start.ts
,会在同目录下生成一个start.js
,代码如下:var sayName = function (name) { console.log("hello " + name); }; sayName('TypeScript');
-
同样,也可以通过
yarn tsc --init
初始化一个tsconfig.json
的配置文件,通过yarn tsc
将ts
文件转为js
文件 -
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 } }
-
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
文件而言,每个文件都是全局作用域,这样就会导致一个问题,当两个文件中有相同的两个变量名,就会导致报错,这里提供两种解决方案:
- 使用自执行函数,形成局部作用域
(function(){
const a: number = 2;
})()
- 使用
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}`);
}
}
- 类的访问修饰符
- 访问修饰符有
public
,private
,protected
, 默认是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
- 类与接口
- 如
Person
与Animal
都具有eat
与run
的属性,那么可以定义两个接口,让其对应的类实现接口 - 为让接口更为简单,一般而言,一个接口只约束一个能力,一个类实现多个接口
- 如
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')