强类型与弱类型
在函数参数上
强类型要求入参必须按照类型来传
fun(int num){}
func(num){}
强类型语言中不允许任意的数据隐式类型转换,而弱类型是允许的
如node 是弱类型
> '100'-50
50
> Math.floor(true)
1
在python中
>>> '100' - 50
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'int'
在语言层面的类型限制
弱类型的问题
-
必须在运行阶段才能发现代码中的类型异常
const obj = {}; obj.foo();
*TypeError: obj.foo is not a function
-
类型不确定造成的典型问题,如果使用强类型语言则不会有这个问题,如要求是数字类型,传入字符串类型则语法不通过
function sum(a,b){ return a+b; } console.log(sum(1,2)); //3 console.log(sum(1,'2')); //12
-
对象属性会转为字符串,若不知道此特点则很奇怪
const obj = {} obj[true] = 123; console.log(obj['true'])
君子约定有隐患,强制要求有保障
强类型的优势
-
错误更早暴露
-
代码更只能,编码更准确
如成员的名称校验 element.innerHtml;
-
重构更牢靠
工具类中的方法改名,强类型在编译期间就会提示错误,弱类型则不会报错,需要在运行阶段才会报错
-
减少不必要的类型判断
function sum(a,b){ if(typeof a !== 'number' || typeof b !== 'number'){ return 'type is not number' } return a + b; }
静态类型语言与动态类型语言
-
静态类型语言:一个变量声明时它的类型就已明确了,不能修改了
-
动态类型:在运行阶段才能够明确变量类型,变量类型可以随时发生变化
如JavaScript中
var foo = 100;
foo='你好'; //ok
cosnole.log(foo)
可以理解为动态类型语言中的变量是没有类型的,变量中存放的值是有类型的.
JavaScript是弱类型的动态类型语言
脚本语言,没有编译环节.即便设计成静态类型语言也无意义,静态类型语言在编译的时候会做静态类型检查,js不需要
flow
JavaScript的类型检查工具(Facebook推出的工具)
通过代码中使用类型注解的方式来表明变量/参数应该是什么类型的
function square(n: number){
retrun n*n;
}
square('100') //类型异常检查
在运行之前通过babel或官方模块去除注解
flow快速上手
flow是以npm模块的方式去工作的
初始化
yarn init --yes
添加模块到该项目
yarn add flow-bin --dev
安装完后可以在node_modules/.bin文件夹内发现flow执行文件,也就是说可以在命令行中执行flow
//@flow
function square(n: number){
retrun n*n;
}
square('100') //类型异常检查
必须在文件的第一行以注释的形式添加@flow的标记,flow才会检测这个文件
//初始化产生.flowconfig配置文件
yarn flow init
// 执行flow 第一次执行慢一点,因为要启动一个后台服务去接收文件
yarn flow
//完成编码后 结束服务
yarn flow stop
编译去除注解
flow注解是在js中编译是会报错的
安装官方的移除注解模块(简单快捷)
yarn add flow-remove-types --dev
//执行 yarn flow-remove-types 源代码所在目录(.是当前目录) -d 输出目录
yarn flow-remove-types . -d dist
这样是src目录下
yarn flow-remove-types src -d dist
babel
//安装 核心模块 和 babel的cli工具(可以用babel命令去编译) preset-flow包含转换flow类型注解的插件
yarn add @babel/core @babel/cli @babel/preset-flow --dev
在项目中添加.babelrc文件
{
"presets":["@babel/preset-flow"]
}
通过yarn运行命令
yarn babel src -d dist
也能移除flow的类型注解
开发工具插件
Flow Language Support
flow类型推断
//@flow
function square(n){
return (n*n); //根据计算 类型推断应该是数字
}
square('你好')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HlIhdpHy-1627926188756)(C:\Users\Kid\AppData\Roaming\Typora\typora-user-images\image-20210701005036991.png)]
flow类型注解
//@flow
let num:number = 123; //在变量后面加冒号 类型名称规定该变量是什么类型的
num = 'Str' //报错
// 函数括号后添加冒号 类型名称 限定该函数的返回值类型,若无返回值是 undefined 则类型应该是void
function fun():number{
return 123; //正常
return '123'; //报错
}
flow原始类型
//@flow
// 原始类型
const a:string = '字符串';
const b:number = Infinity // 100 NAN 数字类型
const c:boolean = false // true
const d:null = null
const e:void = undefined //flow中的undefined是用void表示
const f:symbol = Symbol();
any 表示任易类型,尽量避免使用,不安全,例如两数相加时 字符串拼接的结果
flow数组类型
flow中支持2中数组类型的表示方法
/**
* 数组类型
* @flow
*/
// 第一种
//<数组中每个元素的类型> 泛型
const arr1:Array<number> = [1,2,3,4]; //这里出现字符串不是数字类型则会报错
// 第二种
const arr2:number[] = [1,2,3]
// 元组 固定长度的数组 指定每个元素的类型
const foo:[string,number] = ['1',2];
flow对象类型
/**
* 对象类型
* @flow
*/
//表示当前对象中必须要有foo和bar的成员,而且类型分别为string和number
const obj1:{foo:string,bar:number} = {
foo:'字符串',
bar:123
}
// 如果其中的成员是非必须的,可以在成员名称后面加 ? 表示可有可无的
const obj2:{foo?:string,bar:any} = {
bar:'第二个参数'
}
// 指定键的类型和值的类型 以下是键必须是字符串类型,值必须是数字类型
const obj3:{[string]:number} = {
'1':1
};
flow函数类型
/**
* 函数类型
* @flow
*/
// 类似于箭头函数的函数签名的方式指定回调函数的参数类型与返回值
function foo(callback:(number, string)=>void) {
// 表示 规定callback的参数1是数字类型 参数2是字符串类型 箭头后面是返回值类型 void代表不返回
callback(123,'123')
}
foo( function (number, string){
console.log(number);
console.log(string);
})
flow特殊类型
字面量类型/联合类型/或类型
/**
* 特殊类型
* @flow
*/
// a变量后面的字符串只能被此字符串赋值,赋其他值就会报错
const a:'你好' = '你好'
// 应用可以被多个或的字符串赋值
const status: 'success' | 'error' | 'danger' = 'error'
// type关键字 给类型做别名
type StringOrNumber = string | number;
// 利用此类型别名作为变量
const b:StringOrNumber = '字符串' //100
// maybe类型
// 想要给一个类型 允许赋null或者undefined的时候
const d:?number = null
// 在类型前面加问好相当于如下
const e:number|null|void = undefined
mixed 和 any
mixed接收任易类型的数据,相当于所有类型的联合类型
区别:any是弱类型 而mixed任然是强类型;
/**
* mixed 和 any类型
* @flow
*/
// string|number|boolean ....
function fun(value:mixed){
// 强类型语法上会报错
if( typeof value === 'string'){
value.substr(1)
}
if( typeof value === 'number'){
value*value
}
}
fun('string')
fun(123)
// --------------------------------------
function funAny(value:any){
// 弱类型语法上不会报错
value.substr(1)
value*value
}
funAny('string')
funAny(123)
一般用mixed 更为安全;用any是为了兼容一些老代码;
flow 类型小结
以上是比较常用的类型,如遇到其他的类型可到
或者
查询相关的类型信息
flow运行环境API
因为JavaScript不是独立工作的,例如运行在node环境/浏览器环境;浏览器中有DOM和BOM,node中有各种模块
/**
* 运行环境 API
* @flow
*/
// HTMLElement类型 如果没找到对应的元素会返回null,类型不对应会报错
const element: HTMLElement | null = document.getElementById('app')
vscode中在HTMLElement 右击转到定义能显示对应的声明
TypeScript
typescript是基于JavaScript的一门编程语言 包含(JavaScript、类型系统、es6+)
即使什么特性不知道也可以使用JavaScript的语法使用,例如只想使用es的新特性,使用typescript也是很好的选择
typescript最低能编译到es3版本的代码,兼容性好;任何JavaScript运行环境都支持typescript进行开发
缺点:语言多了很多概念(如接口/泛型/枚举等);但是typescript是渐进式的,什么特性不知道也能当JavaScript使用;周期短的小项目增加一些成本(如类型声明单独编写);
typescript快速上手
yarn初始化项目
yarn init --yes
然后添加typescript模块 作为开发依赖使用
yarn add typescript --dev
.bin目录下的tsc文件是用来编译typescript的命令
/**
* typescript快速上手
*/
const hello = (name: string) => {
console.log(`Hello,${name}`)
}
hello('TypeScript')
hello(100) //这里会报错
使用yarn tsc命令编译
yarn tsc 文件名.ts
编译后生成一个新的js文件,转换成了ES3标准的语法;ts文件内可以使用flow,如果语法不通过,编译也会报错
typescript的配置文件
yarn tsc --init
生成了一个tsconfig.json文件
target 改为es2015输出结果中就不会进行es6转换了
module commonjs模块 导入导出为require 和 export的方式
outDir编译完后输出的路径如"dist"
rootDir:typescript源代码路径
sourceMap: true开启源代码映射,调试的时候使用sourcemap调试
strict:true 开启严格模式 每个变量都要声明对应的类型 哪怕是any的类型
项目根目录运行
yarn tsc
进行编译
typescript的原始类型
/**
* typescript原始类型
*/
const a:string = '字符串'
const b:number = 100
const c:boolean = true
// 非严格模式下 或 strictNullChecks配置为false的时候 与flow 的区别是可以为null
const d:string = null
// void一般用于标记函数无返回值的时候返回undefined的类型
const e:void = undefined
const f:null = null
const g:undefined = undefined
const h:symbol = Symbol() //这里会报错 根据提示 将配置文件tsconfig.json中的target 改为 es2015 或更高版本
标准库就是内置对象所对应的声明,typescript中使用内置对象就必须要引入对应的声明,否则报错
target为es5就不支持es6的特性,所以语法会报错,1.可以改目标的标准库;2.在lib:[“es2015”] 增加
浏览器的BOM和DOM合为"DOM" 即lib:[“es2015”,“DOM”]
中文报错
编译使用 --locale zh-cn
yarn tsc --locale zh-cn
vscode中设置 TypeScript Locale 为:zh-CN即可
一般不建议设置vscode报错为中文,因为报错通过英文查找比较好找到解决办法;
作用域问题
编译的时候是所有文件全局进行编译的,所以不同文件下的全局变量相同名称会报错
/**
* 作用域
*/
//别的文件有该变量名则会报错
const a = 123;
//放在函数的作用域中则不会报错
(function() {
const a = 2;
})
// 所有的成员就会变成这个模块作用域中的局部成员了;
export {}
export {}实际很少用到,绝大多数情况下,每个文件都会以模块的形式去工作
Object类型
- 对象类型不仅仅是对象
- 对象内的属性可以指定类型,不能多也不能少.专业的方式是使用接口
/**
* 对象类型
*/
export {} //确保和其他文件没有成员冲突
const foo:object = null //function(){} //[] //{}
const obj:{num:number,str:string} = {num:1,str:'字符串',more:"多出来会报错"} //少了num和str也会报错
数组类型
类型注解与flow一样
/**
* 数组类型
*/
export {} //确保和其他文件没有成员冲突
const arr1: Array<number> = [1,23,3]
const arr2:number[] = [1,2,3]
function sum(...args:Array<number>){
let result = args.reduce((pre,cur)=>pre+cur,0);
console.log("结果",result)
}
// sum(1,23,3,"4") //有字符串非数字类型报错
sum(1,23,3)
元组类型
明确元素数量与元素类型的数组
/**
* 元组类型
*/
export {} //确保和其他文件没有成员冲突
const arr:[string,number] = ['你好',123];
// const arr2:[number,string] = ['1',2,3] //多了元素或着类型不对也会报错
// Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组
// 此方法源码也是使用元组类型 :[string, number]
Object.entries({
a:123,
b:456
})
枚举类型
JavaScript中没有该类型,一般使用对象
枚举声明 使用等号指定;不用等号指定,从0开始累加;其中一个指定了具体的值,后面会根据前面的值进行累加
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t5e3GlRv-1627926188758)(C:\Users\Kid\AppData\Roaming\Typora\typora-user-images\image-20210721005239599.png)]
/**
* 枚举类型
*/
export {} //确保和其他文件没有成员冲突
// const postStatus = {
// success:1,
// err:2,
// ing:0,
// }
// 枚举声明 使用等号指定
// enum postStatus{
// success = 1,
// err = 2,
// ing = 0,
// }
// 不用等号指定,从0开始累加
// enum postStatus{
// success, //0
// err = 44,
// ing, //45
// }
// 字符串初始化必须给每个成员指定值
// 增加const常量声明,则不能用索引的方式使用
const enum postStatus{
success = 'aaa',
err = 'bbb',
ing = 'ccc',
}
const post={
title:"审核中",
statu:postStatus.ing, //1 审核成功 2 审核失败
}
// 编译后
var post = {
title: "审核中",
statu: "ccc" /* ing */, //1 审核成功 2 审核失败
};
// 具体的数值 名称以注释的方式出现
函数类型
函数定义的方式
1.函数声明
2.函数表达式
/**
* 函数类型
*/
export {} //确保和其他文件没有成员冲突
// 参数名称后面加一个小问号,表示该参数是可选参数
// es6特性可以给参数赋默认值,赋默认值后就不是必选参数了
// 接收任意个数的参数 使用es6的rest操作符
function fun1(a:number = 10, b?:number,...rest:number[]):string {
return "圆括号后面冒号类型表示返回值的类型"
}
fun1(1,2)
// fun1(1,2,3) //报错,参数个数不一致
fun1(1) //语法通过
// fun1('1') //报错类型不一致
// -------------------------------表达式
const func2 = function(a:number,b:number):string{
return '字符串'
}
const func3: (a: number, b: number) => string = function(a:number,b:number):string{
return '字符串'
}
任易类型
any 类型是不安全的,语法上不同类型赋值不会报错,除非兼容以前的老代码
/**
* 任易类型
*/
export {} //确保和其他文件没有成员冲突
function stringify(value:any){
return JSON.stringify(value);
}
stringify(123);
stringify('123');
stringify(true)
let foo:any = 'string';
foo = 123;
foo.bar();
隐式类型推断
最好给每个变量添加明确的类型注解,更有利于代码的阅读
/**
* 隐式类型推断
*/
export {} //确保和其他文件没有成员冲突
let age = 18; //被typescript推断为数字类型
age = 'String'; // 报错 不能将类型“string”分配给类型“number”
// 如果typescript不能推断一个变量类型,则是any类型
let foo; //any类型
foo = 123;
foo = '123';
类型断言
类型断言不是类型转换,是遍以上的概念,不是代码运行时的概念
/**
* 类型断言 */
export {} //确保和其他文件没有成员冲突
// 假定nums是来自接口
const nums = [110,120,119,112,undefined]
// find 寻找数组中符合条件的第一个元素,没有返回undefined;空数组不执行;不改变原数组
const res = nums.find(i=>i>200); //有可能会返回undefined
// const square = res * res; //如果返回的undefined 这里则会报错
//类型断言 为数字类型 推荐使用该方式
const num1 = res as number;
const num2 = <number>res; //JSX下不能使用
typescript接口
一种抽象的概念,约束对象的结构,如预定一个对象具体有哪些成员,成员的类型
typescript接口在编译运行后并没有别的意义,只是在编写时候的约束作用
/**
* 接口 */
export {} //确保和其他文件没有成员冲突
// interface关键字 声明一个接口 这里是没有括号的()
interface Post {
title:string; // 可以使用逗号分割, 更标准语法是分号分割; 和JavaScript中的分号一样可以省略
content:string,
subtitle?:string, //可选成员? 后加问号表示该成员可有可无
readonly summary:string, //只读成员,初始化后不可修改
}
// 设置post参数类型为Post接口
function printPost(post:Post) {
console.log(post.title);
console.log(post.content);
}
printPost({
title:"标题",
content:"内容",
summary: '初始内容'
// more:123 //多了会报错
})
const newObj:Post = {
title:"标题",
content:"内容",
summary: '初始内容'
}
// newObj.summary = '二次修改' // 报错:无法分配到 "summary" ,因为它是只读属性
//----------------------------------
interface Cache {
[keyName:string]: string //keyName是自定义的 :string表示key的类型为string : 值的类型
}
const obj:Cache ={
"这":"值"
}
可选成员 后面 ? 问号表示
只读成员 前面 readonly表示
接口键值对类型 {[键名:键类型]:值类型}
类
es6增加的"类"的特性typescript中都能使用,typescript中的类还有新增的特性
在typescript中要明确声明类的属性,而不是构造函数中动态添加;
在typescript中类的属性必须要有初始值,可以在=号后面赋值,或者在构造函数中初始化
类的属性在使用前必须要声明,目的是为了给属性的类型进行标注
/**
* 类
*/
export {} //确保和其他文件没有成员冲突
class Person{ //声明类 这里是没有括号的()
//es2016 即es7中的语法
name: string = '初始值';
age: number;
constructor(name:string,age:number){ //构造函数
this.name = name; //报错 类型“Person”上不存在属性“name”
this.age = age;
}
sayHi(msg: string): void{
console.log(`这是${this.name},${this.age}`)
}
}
访问修饰符
public 公共的,默认的修饰符
private 私有的,在类的内部访问
protected 受保护的,类的内部和子类中访问
/**
* 类的访问修饰符
*/
export {} //确保和其他文件没有成员冲突
class Person{ //声明类
public name: string = '初始值'; // 默认是public的修饰符
private age: number;
protected gender: string;
constructor(name:string,age:number){ //构造函数
this.name = name;
this.age = age;
}
sayHi(msg: string): void{
console.log(`这是${this.name},${this.age}`)
}
}
const tom = new Person('tom',18);
// console.log( tom.age ) //报错 属性“age”为私有属性,只能在类“Person”中访问
// console.log( tom.gender ) //报错 属性“gender”受保护,只能在类“Person”及其子类中访问。
// Student 继承了 Person
class Student extends Person{
constructor(name:string,age: number){
super(name,age);
console.log(this.gender) // 不报错
}
}
类 只读属性 readonly
如果只读属性要修饰的成员已经有访问修饰符了, readonly要跟在访问修饰符的后面
/**
* 类的只读属性
*/
export {} //确保和其他文件没有成员冲突
class Person{ //声明类
protected readonly gender: string; //readonly修饰的只读属性不能再修改
constructor(){ //构造函数
this.gender = '男'; //要么在声明的时候初始化要么在构造函数初始化
}
}
// Student 继承了 Person
class Student extends Person{
constructor(){
super();
this.gender = '女'; //报错 无法分配到 "gender" ,因为它是只读属性。
}
}
类与接口
interface 声明一个接口,接口内的方法是不做具体实现的
implements 实现一个接口,必须要有接口对应的成员,否则报错
implements Eat,Run 逗号间隔实现多接口
接口最好细分一些,一个接口代表一个能力;让一个类实现多个接口较为合理
/**
* 类与接口
*/
export {} //确保和其他文件没有成员冲突
interface Eat{
eat(food: string): void
};
interface Run{
run(way:string):void
}
class Person implements Eat,Run {
eat(food:string):void{
console.log(`优雅的进餐${food}`);
};
run(myWay:string):void{
console.log(`人类直立行走在${myWay}`);
}
}
抽象类
在某种程度上与接口类似,可以约束子类中必须要有某个成员;
不同的是抽象类能够包含具体的实现,而接口不行;
一般比较大的类使用抽象类,如:动物
使用abstract声明一个抽象类,声明后只能被继承不能通过new的方式创建实例;
抽象类中能定义抽象方法 abstract fun() 抽象方法也不需要写方法体;子类必须实现父类的抽象方法
/**
* 抽象类
*/
export {} //确保和其他文件没有成员冲突
abstract class Eat{
eat(food: string): void{
console.log(`大多数动物用嘴巴吃${food}`);
};
// 抽象方法,继承该抽象类的类都必须实现该方法
abstract run(mode: string):string;
};
class Person extends Eat {
otherEat(food:string):void{
console.log(`优雅的进餐${food}`);
};
run(myWay:string):string{
let str = `人类直立行走在${myWay}`
console.log(str);
return str;
}
}
泛型
定义函数/接口/类的时候不去指定类型,使用的时候再指定具体的类型
目的:极大程度的复用代码
/**
* 泛型
*/
export {} //确保和其他文件没有成员冲突
// 创建一个指定长度数字类型的数组
function createNumberArray(length,value:number):number[]{
let arr = Array(length).fill(value);
return arr;
}
// 但是如果要创建一个字符串的数组以上就不符合了 所以我们使用泛型;
// <T>表示泛型 T也可以是其他的值,一般用T表示
function createArray<T>(length,value:T):T[]{
// 指定数组内的元素是什么类型,否则是any类型
let arr = Array<T>(length).fill(value)
return arr;
}
createArray(3,100)//[100,100,100]
类型声明
实际开发中难免用到第三方的npm模块;可以尝试安装对应模块的类型声明 一般是@types/模块名
如loadsh模块(工具模块) 、 query-string(解析query的字符串)
yarn add lodash
yarn add query-string
没有对应的类型声明可以用declare语句声明;declare语句声明可以在函数原来定义的时候通过declare声明这个函数的入参和返回值类型
.d.ts是typescript中用来类型声明的文件
/**
* 类型的声明
*/
import {camelCase} from 'loadsh'
const qs = require('query-string')
// declare 声明函数的参数与返回值类型
declare function camelCase(str: string): string
const str = camelCase('www.baidu.com?a=1&c=false')
console.log(str) //wwwBaiduComA1CFalse
const query = qs.parse('www.baidu.com?a=1&c=false');
console.log(query) //
export {} //确保和其他文件没有成员冲突