❝
快速入门TypeScript,理解类型系统以及JavaScript自有类型系统的问题
❞
理解类型系统
理解JavaScript自有类型系统的问题。先要理解以下的类型系统
强类型:语言层面限制函数的实参类型必须与形参类型相同
class Main{ static void foo(int num){ System.out.println(num); } //Main.foo(100)//正确的 //Main.foo("100")//错误的 }
弱类型:语言层面上不会限制实参的类型
function foo(num){ console.log(num) } //语法上不会报错 可以传入任意类型 foo(100);//ok foo("100");//ok
由于这种强弱类型之分根本不是某一个权威机构的定义,一般描述强类型有更强的类型约束,而弱类型中几乎没有什么约束。
强类型语言中不允许任意的隐式类型转换,而弱类型语言则允许任意的数据隐式类型转换
变量类型允许随时改变的特点,不是强弱类型的差异
静态类型:一个变量声明时它的类型就是明确的,声明过后,它的类型就不允许再修改了
动态类型:在运行阶段才能够明确变量类型,而且变量的类型随时可以变化
JavaScript 是弱类型且动态类型的语言 【任性】缺失了类型系统的可靠性【不靠谱】
早期JavaScript应用简单,JavaScript是一个脚本语言,没有编译环节的(静态类型语言需要编译 检查的)在大规模应用下,JavaScript的灵活多变的优势,变成了短板。
弱类型的问题:
在小项目中,我们可以通过约定的方式进行设置,但是在大规模项目这种约定就会存在很大的隐患
//JavaScript 弱类型产生的问题 const obj = {} //运行到此就会报错 抛出异常 这是一个隐患 而强类型的语言直接在语法层面上抛出错误 setTimeout(() => { obj.foo(); }, 1000); //如下 传递不同类型的参数 函数的作用就完全不同了 如果使用强类型的语言 会直接限制参数的类型 function sum(a,b){ return a+b; } sum(100,100);//200 sum(100,"100");//100100 //对象属性名会自动转换为字符串 const obj = {}; obj[true] = 100; console.log(obj['true']);//对对象索引器的错误用法
强类型的优势:
Flow
Flow 静态类型检查方案
yarn add flow-bin --dev 安装flow
对代码添加标记:// @flow
yarn flow init 初始化flow
yarn flow 执行flow命令检测代码中的类型问题
/* @flow */ function s(num:number):void{ console.log(num); } s(123);
JavaScript 没有编译阶段,直接运行的,而Flow就是给JavaScrip增加了编译阶段来检查类型错误
一般注解类型只是在开发阶段使用,在线上环境是不需要的那么在线上环境的时候需要移除注解,
安装模块:yarn add flow-remove-types --dev
执行命令 :yarn flow-remove-types src -d dist
就会把类型注解去掉生成的代码如下,dist目录下的代码就可以提供到线上使用
function sum (a:number,b:number ) { return a + b; }//flow 编译之后的代码 移除类型注解 function sum (a , b ) { return a + b; } sum(100 ,100 ); sum('100' ,'100' );
yarn add @babel/core @babel/cli @babel/preset-flow --dev 安装babel模块来实现编译移除注解类型
配置.babelrc 文件
{ "presets" : ["@babel/preset-flow" ] }
然后执行:yarn babel src -d dist 将src中的源代码移除类型注解,放到dist目录下
Flow language support 插件vscode 支持检测类型错误 而不用再去运行命令:yarn flow
/* 原始类型 @flow */ const a:string = 'foobar'; const b:number = 100; const b1:number = NaN; const b2:number = Infinity; const c:boolean = true; const d:null = null; const e:void = undefined; const f:symbol = Symbol(); function s():void{ } function ss():number{ return 100; }
/* 数组类型 @flow */ const arr:Array <number> = [1 ,2 ,3 ];//<T> 泛型 const arr2:number[] = [1 ,2 ,3 ,4 ];//固定长度的数组 const foo:[string,number] = ['foo' ,123 ];//第一个元素必须是字符串 第二个元素是数字
/* 对象类型 @flow */ //定义对象的成员类型方式如下 const obj:{foo :string,bar :number} = { foo :'string' ,bar : 123 }//上述定义成员类型,成员必须定义否则报错 可以通过?:的方式 成员不是必须定义的 const obj1:{foo?:string,bar :number} = { bar :123 }//设置对象属性键的类型限制和值的类型限制 const obj2:{[string]:string}={} obj2.key='value' ; obj2.age= '14' ;
/* 函数类型 @flow */ //设置函数的参数类型 以及返回值类型 function s (num:number ):void { console .log(num); } s(123 );function ss ( ):number { return 100 ; }//回调函数的类型限制(string,number)=>void 参数类型以及返回值类型 function foo (callback:(string,number )=>void ) { callback('zce' ,123 ); } foo(function (name,num ) { console .log(name,num); });
/* 特殊类型 @flow */ //字面量类型 a 只能等于foo const a:'foo' ='foo' ;//type 可以等于'success' | 'warning' | 'danger' const type:'success' | 'warning' | 'danger' = 'success' ;//同时还可以使用多个类型 type StringOrNumber = string | number;const b : StringOrNumber = '12' ;//100 const gender:?number = 100 ;//? maybe类型 可以接受undefined null
/* 任意类型 @flow */ //mixed 强类型 就可以传递任意的类型 function passMixed (value:mixed ) { //需要添加类型判断 if (typeof value === 'number' ) { value * value; } if (typeof value === 'string' ) { value.substr(1 ); } } passMixed(200 ); passMixed('100' );//any 弱类型 也可以表示任意类型 一般不推荐使用any function passAny (value:any ) { //any可以使用任何方法 value 其实还是弱类型的 value * value; value.substr(1 ); } passAny(200 ); passAny('100' );
/* 运行环境API 类型 https://github.com/facebook/flow/blob/master/lib/dom.js @flow */ //DOM BOM Node的内置API等 都有一些类型限制 const element:HTMLElement | null = document .getElementById("id" );
以上就是Flow提供的静态类型检查方案,其实TypeScript写法与其类似。
TypeScript
TypeScript解决JavaScript类型系统的问题,TypeScript大大提高代码的可靠程度
TypeScript 可以在任何一个JavaScript中的运行环境中都支持
缺点一:语言本身多了很多概念
缺点二:项目初期,会增加一些成本
TypeScript 属于渐进式的
TypeScript 最终会编译成JavaScript并且支持到ES3的标准
yarn add typescript --dev 项目中添加typescript模块
yarn tsc 01-getting-start.ts 编译ts文件 ,tsc命令就是编译ts文件
/* 可以完全按照javascript进行编码 */ const hello = (name:string ) => { console .log(`hello ${name} ` ); } hello('zce' );
编译后的文件如下:
"use strict" ;/* 可以完全按照javascript进行编码 */ var hello = function (name ) { console .log("hello " + name); }; hello('zce' );//# sourceMappingURL=01-getting-start.js.map
编译整个项目,创建配置文件:yarn tsc --init
配置文件:TS 还有很多配置 可以自行去官网查阅 下面的配置是常用的配置方式
"target" : "es5" ,//编译后的标准js代码 "module" : "commonjs" ,//模块化 "outDir" : "dist" ,//编译后的目录 "rootDir" : "src" ,//源代码目录 "sourceMap" : true ,//源代码映射 "strict" : true ,//开启所有严格检查选项 "lib" : ["ES2015" ,"DOM" ],//配置标准库 如果要使用ES2015的新的标准就需要引入对应的标准库包括DOM BOM等
命令:yarn tsc 编译整个项目,编译后的js文件就会存放到dist目录中
/* 原始类型在TS中的应用 */ const a:string = 'foo' ;const b:number = 100 ;//NaN Infinity const c:boolean = true ;//false // const d:string = null;//严格模式下 不允许为null const e:void = undefined ;//严格模式下 不允许为null const f:null = null ;const g:undefined = undefined ;//如果使用ES5标准库,而这时使用ES6的标准库新的类型会出现错误。 //解决方案一:改为ES2015标准库; //解决方案二:"lib": ["ES2015","DOM"] const h:symbol = Symbol ();Array ;Promise ;//DOM API需要在lib中引入DOM console .log();//标准库就是内置对象所对应的声明
export {}const foo:object = function ( ) {}//[] {} 可以接受对象 数组 函数 //限制对象成员类型 对象的成员 const obj:{foo :number,bar :string} = {foo :123 ,bar :"string" };
const arr1:Array<number> = [1,2,3,]; const arr2:number[] = [1,2,3,]; //---------累加 function sum(...args:number[]){ //如果使用js就要判断参数是否为Number类型 //ts只需要添加一个数字的类型注解即可 return args.reduce((prev,current)=>prev + current,0); } // sum(1,2,3,4,'foo');
元组:就是一个明确元素数量以及元素类型的一个类型 各个元素的类型不必要完全相同
const tuple:[number,string] = [18,''];//类型不相符 或者超出元素的数量都会报错 const [age,name] = tuple; //元组类型现在非常常见了 //比如 entries 得到一个键值数组,键值数组就是一个元组[string,number] //注意entries 属于ES2017的一个标准库 Object.entries({ foo:123, bar:456 });
一般定义类型的写法:
const PostStatus={ Draft:0, Unpublished:1, Published:2 }
枚举类型的写法
//默认值是:0开始 依次累加 可以不用指定值 //枚举值可以是字符串 但是字符串无法像数字一样自增长 需要给每一个枚举赋值 //常量枚举 以及 基本枚举的编译情况是不同的 注意 const enum PostStatus{ Draft,//0 Unpublished,//1 Published//2 } const post={ title:"Hello TypeScript", content:"", status:PostStatus.Draft//0 1 } // PostStatus[0]// => Draft
function stringify(value:any){ return JSON.stringify(value); } stringify('100'); stringify(100); //any 动态类型 ts不会对他进行类型检查 let foo:any = 'string'; foo = 100; foo.bar();
//可选参数或者默认参数 一定在参数列表的最后 function func1(a: number, b?: number,...rest:number[]): string { return "func1"; } func1(100,200); func1(100); // func1();
函数表达式,回调函数的约束在TS中可以这样定义:(a:number,b:number) => string
//函数表达式 //回调函数约束 const func2 : (a:number,b:number) => string = function(a:number,b:number):string{ return 'func2'; }
TypeScript可以自动推荐类型,一旦确定类型就不允许改变类型
let age = 18;//推断为了 number // age = 'number';//报错 let foo;//没有赋值就是any类型 //可以给任意类型的值 语法上不会报错 foo = 100; foo = 'string'; //建议每个变量添加更直观的类型
const nums = [100,200,199,112]; const res = nums.find(i=>i>1000);//推断res可能找不到 // const square = res * res; const num1 = res as number;//告诉编译器 我一定是number 不用担心 const num2 = <number>res;
接口 可以约定一个对象的结构,可以约定有哪些成员 TS 只是进行了约束 在编译成JavaScript时实际上没有任何意义和普通对象一样
interface Post { title: string; content: string; //可选成员 subtitle?: string; //只读成员 readonly summary:string; } function printPost(post:Post){ console.log(post.title); console.log(post.content); } //subtitle是可选属性 可以不用传入 printPost({title:'hello',content:'a javascript',summary:'12'}); const hello: Post = { title: "hello", content: "a javascript", summary:'A JavaScript' }; hello.summary = "123";//报错 因为summary属性是一个只读属性 不能修改属性值
定义动态成员的key类型 以及值的类型 定义的成员必须一致否则会报错
interface Cache{ [prop:string]:string //[prop:string] -> key的类型 :string -> 值的类型 } const cache:Cache = {} cache.foo = "value"; // cache.bar = 123;//报错
类 描述一类事物的抽象特征 ES6以前通过 函数+原型来模拟的类
class 在ES6中就添加了这一个特性,而TypeScript在ES6的基础上对class添加了访问修饰符,类的属性必须要先声明属性并且必须有一个初始值。
class Person{ //类的属性必须有初始值 或者构造函数中赋值否则会报错 public name:string;//默认public 公有属性 private age:number;//private 私有属性只能在类内部访问 protected readonly gender:boolean;//protected 保护 只有子类可以访问 //readonly 只读属性 通过=或者构造函数初始化就不允许再被修改了 constructor(name:string ,age:number){ //直接使用this.name 会报错 TS要求明确声明属性 声明的属性必须有初始值可以在=后面赋值,或者在构造函数中对他赋值 this.name = name; this.age = age; this.gender = true; } sayHi(msg:string){ // this.gender = false; console.log(`I am ${this.name} is age:${this.age}`); } logSay(){ this.sayHi('你好啊'); } } class Student extends Person{ //构造函数声明了private 外部就不允许尽心实例化类了 private constructor(name:string,age:number){ super(name,age); console.log(this.gender); // // this.gender = false; } //可以通过内部new实例返回交给外部调用 static create(name:string,age:number){ return new Student(name,age); } } const tom = new Person('tom',18); console.log(tom.name); // console.log(tom.gender); // console.log(tom.age); // const jake = new Student('jake',18); const jake = Student.create('jake',18);
类实现接口,代码如下:
interface Eat{ eat(foo:string):void } interface Run{ run(distance:number):void } class Person implements Eat,Run{ eat(foo:string):void{ console.log(`优雅的进餐${foo}`); } run(distance:number):void{ console.log(`直立行走${distance}`); } } class Animal implements Eat,Run{ eat(foo:string):void{ console.log(`咕噜咕噜的吃${foo}`); } run(distance:number):void{ console.log(`爬行${distance}`); } }
抽象类 可以包含一些方法的具体的实现
abstract class Anima{ eat(foo:string):void{ console.log(`吃${foo}`); } //抽象方法 abstract run(d:number):void; } class Dog extends Anima{ //实现父类的抽象方法 run(d: number): void { console.log(`走:${d}`); } }
TypeScript还引入了泛型的概念
function crateNumberArray(len:number,value:number):number[]{ //<number> 存放number类型的数据 const arr = new Array<number>(len).fill(value); return arr; } const res = crateNumberArray(3,100); //res => [100,100,100] //创建string类型的数据 function crateStringArray(len:number,value:string):string[]{ //<number> 存放number类型的数据 const arr = new Array<string>(len).fill(value); return arr; } //通过泛型解决上述代码中的冗余部分 function createArray<T>(len:number,value:T):T[]{ const arr = new Array<T>(len).fill(value); return arr; } createArray<number>(1,100); createArray<string>(10,'10');
TS 与 JS混合开发,涉及到老代码等逻辑,这里就需要用到类型声明,比如一般我们使用的第三方模块 没有用ts实现这时候就需要 使用模块的类型声明
比如lodash的第三方库,提供了类型声明文件,只需要安装即可 npm install @types/lodash
import {camelCase} from 'lodash'; //query-string 模块里面已经包含了类型声明的模块 import qs from 'query-string'; // qs.parse(); //声明函数的类型 明确的声明函数的类型 // declare function camelCase (input:string) : string; //npm install @types/lodash 或者安装类型声明模块 const r = camelCase('hello worle');