❝快速入门TypeScript,理解类型系统以及JavaScript自有类型系统的问题 ❞
理解类型系统
理解JavaScript自有类型系统的问题。先要理解以下的类型系统
- 强类型与弱类型(类型安全)
强类型:语言层面限制函数的实参类型必须与形参类型相同
代码语言:javascript复制class Main{
static void foo(int num){
System.out.println(num);
}
//Main.foo(100)//正确的
//Main.foo("100")//错误的
}
弱类型:语言层面上不会限制实参的类型
代码语言:javascript复制function foo(num){
console.log(num)
}
//语法上不会报错 可以传入任意类型
foo(100);//ok
foo("100");//ok
由于这种强弱类型之分根本不是某一个权威机构的定义,一般描述强类型有更强的类型约束,而弱类型中几乎没有什么约束。
强类型语言中不允许任意的隐式类型转换,而弱类型语言则允许任意的数据隐式类型转换
变量类型允许随时改变的特点,不是强弱类型的差异
- 静态类型与动态类型(类型检查)
静态类型:一个变量声明时它的类型就是明确的,声明过后,它的类型就不允许再修改了
动态类型:在运行阶段才能够明确变量类型,而且变量的类型随时可以变化
- JavaScript自有类型系统的问题
JavaScript 是弱类型且动态类型的语言 【任性】缺失了类型系统的可靠性【不靠谱】
早期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增加了编译阶段来检查类型错误
- flow 编译移除注解
一般注解类型只是在开发阶段使用,在线上环境是不需要的那么在线上环境的时候需要移除注解,
安装模块:yarn add flow-remove-types --dev
执行命令 :yarn flow-remove-types src -d dist
就会把类型注解去掉生成的代码如下,dist目录下的代码就可以提供到线上使用
代码语言:javascript复制function sum(a:number,b:number){
return a b;
}
//flow 编译之后的代码 移除类型注解
function sum(a , b ) {
return a b;
}
sum(100,100);
sum('100','100');
- babel
yarn add @babel/core @babel/cli @babel/preset-flow --dev
安装babel模块来实现编译移除注解类型
配置.babelrc 文件
代码语言:javascript复制{
"presets": ["@babel/preset-flow"]
}
然后执行:yarn babel src -d dist
将src中的源代码移除类型注解,放到dist目录下
- flow 开发工具插件
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 类型
/*
运行环境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');
编译后的文件如下:
代码语言:javascript复制"use strict";
/*
可以完全按照javascript进行编码
*/
var hello = function (name) {
console.log("hello " name);
};
hello('zce');
//# sourceMappingURL=01-getting-start.js.map
- TypeScript 配置文件
编译整个项目,创建配置文件:yarn tsc --init
配置文件:TS 还有很多配置 可以自行去官网查阅 下面的配置是常用的配置方式
代码语言:javascript复制"target": "es5",//编译后的标准js代码
"module": "commonjs",//模块化
"outDir": "dist",//编译后的目录
"rootDir": "src",//源代码目录
"sourceMap": true,//源代码映射
"strict": true,//开启所有严格检查选项
"lib": ["ES2015","DOM"],//配置标准库 如果要使用ES2015的新的标准就需要引入对应的标准库包括DOM BOM等
命令:yarn tsc
编译整个项目,编译后的js
文件就会存放到dist
目录中
- 原始类型在TS中的应用
/* 原始类型在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();
//标准库就是内置对象所对应的声明
- Object类型
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');
- 元组类型
元组:就是一个明确元素数量以及元素类型的一个类型 各个元素的类型不必要完全相同
代码语言:javascript复制const tuple:[number,string] = [18,''];//类型不相符 或者超出元素的数量都会报错
const [age,name] = tuple;
//元组类型现在非常常见了
//比如 entries 得到一个键值数组,键值数组就是一个元组[string,number]
//注意entries 属于ES2017的一个标准库
Object.entries({
foo:123,
bar:456
});
- 枚举类型
一般定义类型的写法:
代码语言:javascript复制const PostStatus={
Draft:0,
Unpublished:1,
Published:2
}
枚举类型的写法
代码语言:javascript复制//默认值是: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可以自动推荐类型,一旦确定类型就不允许改变类型
代码语言:javascript复制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时实际上没有任何意义和普通对象一样
代码语言: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类型 以及值的类型 定义的成员必须一致否则会报错
代码语言:javascript复制interface Cache{
[prop:string]:string //[prop:string] -> key的类型 :string -> 值的类型
}
const cache:Cache = {}
cache.foo = "value";
// cache.bar = 123;//报错
- 类 Class
类 描述一类事物的抽象特征 ES6以前通过 函数 原型来模拟的类
class 在ES6中就添加了这一个特性,而TypeScript在ES6的基础上对class添加了访问修饰符,类的属性必须要先声明属性并且必须有一个初始值。
代码语言:javascript复制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);
类实现接口,代码如下:
代码语言:javascript复制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}`);
}
}
抽象类 可以包含一些方法的具体的实现
代码语言:javascript复制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还引入了泛型的概念
代码语言:javascript复制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');