TypeScript基础速览
TypeScript
TypeScript
解决JavaScript
类型系统的问题TS大大提高代码的可靠程度。JavaScript
自有类型系统的问题,所以我们可以使用TypeScript
来健壮我们的代码。首先我们需要了解强类型和弱类型语言的区别。
-
强类型:语言层面限制函数的实参类型和形参类型类型必须一致,可以说强类型不允许随意的隐式转换。
-
弱类型:若类型语言不会限制实参的类型,允许隐式类型的转换
前置知识
静态类型与动态类型
-
静态类型:一个变量声明时它的类型就是明确的,声明过后,他的类型就不允许再修改。
-
动态类型:在运行阶段才能明确变量类型,变量的类型随时可以改变。也可以说变量是没有类型的,变量中存放的值是有类型的。
JavaScript类型系统特征
JavaScript
是弱类型且动态语言,JavaScript
早期需求很简答;脚本语言, 没有编译环节。
而强类型的优势在于:
1、错误更早暴露
2、代码更智能,编码更准确
3、重构更牢靠
4、减少不必要的类型判断
TypeScript概述
TypeScript
是JavaScript
的超集,同时TypeScript
功能更强大,生态也健全更完善。说了那么多优点,它的缺点是语言本身多了许多概念(接口、泛型等),在项目初期,TS
会增加一些成本。
准备工作(安装与编译)
全局安装
npm install -g typescript //mac下安装前面需要加sudo。也可以在项目中局部安装
编译
- 可以执行下面命令进行编译,会在当前目录下产生一个
test.js
文件
tsc test.ts
这样可能还是比较麻烦,我们可以借助插件方便我们提高效率:
npm install -g ts-node
安装完后只需执行ts-node test.ts
就可以在控制台查看输出结果。
tsconfig.json配置
生成配置文件
- 我们可以通过tsc --init去生成ts配置文件:
tsc --init
编译文件
- 会生成一个t
sconfig.json
文件,我们在ts文件中可以随便写点什么:
//测试tsconfig.json
const test:string='tsconfig.json';
- 然后打开
tsconfig.json
文件,找到complilerOptions
属性下的removeComments:true
(这个配置是编译后不输出注释),取消掉注释,然后执行命令:
tsc
- 这时候打开生成的js文件,发现没有注释,说明成功。
tsconfig.json配置项
下面给列一下常用的配置项,更多可以查看 配置查询网站
include 、exclude 和 files
- 如果有多个ts文件,只想编译一个可以在ts配置项中加入
include
:
"include":["text.ts"],
- 如果想除了某个文件不编译,剩下的都编译,可以使用
exclude
:
"exclude":["text.ts"],
files
和include
没有什么区别:
"files":["text.ts"],
removeComments
- 这个配置意思就是编译后不输出注释
strict
- 这个设置为true,就代表严格执行ts语法,要严格按照ts语法来编写。
noImplicitAny
- 允许你的注解类型 any 不用特意表明,如果此时设置了true,看例子:
//这时候编译会报错
function test(name) {
return name;
}
//正确
function test(name:any) {
return name;
}
strictNullChecks
- 意思就是,不强制检查null类型,此时如果配置为true看例子:
//此时就不会报错
const test: string = null;
outDir和rootDir
- 此项配置是来指定文件目录和打包后存放目录,rootDir为文件目录,outDir为打包后保存的目录
{
"outDir": "./build" ,
"rootDir": "./src" ,
}
编译ES6语法
- 可以使用
target
和allowJs
,target默认为true
"target":'es5' , // 可以转化为目标标准
"allowJs":true, // 这个配置项的意思是联通
sourceMap
sourceMap
简单说,Source map
就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。调试的时候就可以使用sourceMap
文件来调试ts源代码。
noUnusedLocals
- 设置
noUnusedLocals
为true,编译代码:
const name:tring='111';
export const age = "text";
- 这时候就会报错,因为有
name
变量没有使用。
原始数据类型
let a:string='lonjin';
let b:number=100; //NaN Infinity
let c:boolean=true;
let g:void=undefined; //空值,常用于表述没有任何返回值的函数
let h:null=null;
let i:undefined=undefined;
在非严格模式(strictNullChecks
)下,string
, number
, boolean
都可以为null
let d:string=null;
let e:number=null;
let f:boolean=null;
如果想使用Symbol
,必须在tsconfig.json
中修改lib
包含ES2015
:
let j:symbol=Symbol();
如果想使用console.log
,也需要在tsconfig.json
中lib
中添加dom
选项
//tsconfig.json
"lib": ["es2015","dom"],
//index.ts
console.log(111)
TypeScript作用域问题
在ts
中,默认文件中的成员会作为全局成员,所以多个文件中有相同成员就会出现冲突(尤其在小程序项目中使用ts
时),解决方法如下:
- 方法1:创建一个立即执行函数,隔离作用域
(function () {
const a = 123
})()
- 方法2:在当前文件使用
export
,也就是把当前文件变成一个模块。在小程序中,每个page
下的index.ts
中如果不写export
就会报作用域错误。
const a = 123
export {}
TypeScript中Object类型
ts
中的Object
类型并不单指对象类型,而是泛指非原始类型,也就是对象、数组、还有函数。
// object 类型是指除了原始类型以外的其它类型,所以可以为函数,数组这种值
let obj:object=function(){};
let obj2:object=[1,2,3];
如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」
let obj3:{name:string,age:number}={name:'lonjin',age:18}
TypeScript中数组类型
数组类型的两种表示方式:
//方式1
let arr:Array<number>=[1,2,3,4,5];
//方式2
let arr2:number[]=[1,2,3,4,5];
如果数组中又有number类型又有string类型,则可以用|符号来区别定义
let arr:(number|string)[]=['tom',1];
项目中经常遇到数组中有对象的存在,可以使用下面方法去定义:
let arr:{name:string,age:number}[]=[
{name:'tom',age:18}
]
这样写起来会很麻烦,我们可以使用ts
中的类型别名来解决这个问题(后面会详细讲解)
type PeopleType={name:String,age:Number};
let arr:PeopleType[]=[
{name:'lonjin',age:18}
]
元组类型
在数组中如果里面又有string和number,可以使用|来进行定义,但一定程度上并不严格。比如改成下面这种格式:
let arr:(number|string)[]=[111,'222',111];
ts
并没有报错,如果想要严格限制,则可以使用元组来进行约束,元组就是明确元素数量和类型的一个数组
let arr:[number,string,number]=[111,'222',111];
Enum枚举
平时我们可能需要定义多种状态来表示对应的含义,大多数情况下我们会这么写:
const Status={
ONE:0,
TWO:1,
THREE:2
}
function getName(status:any){
if(status===Status.ONE){
return 'one'
}else if(status===Status.TWO){
return 'two'
}else{
return 'three'
}
}
这时候枚举就派上用场了:
//默认为数字,从0开始
enum Status{
one,
two,
three,
}
console.log(Status.one,Status.two,Status.three)
//0 1 2
//也可以为字符串
const enum PropStatus{
one='a',
two='b',
three='c'
}
console.log(PropStatus.one,PropStatus.two,PropStatus.three)
//a b c
细心的童鞋可以看到我在后面的枚举前面加了const
,我们编译一下看看有什么不同:
var Status;
(function (Status) {
Status[Status["one"] = 0] = "one";
Status[Status["two"] = 1] = "two";
Status[Status["three"] = 2] = "three";
})(Status || (Status = {}));
console.log(Status.one, Status.two, Status.three);
console.log("a" /* one */, "b" /* two */, "c" /* three */);
- 可以看到,第一个没加
const
,它编译后会入侵编译结果
,而如果使用常量枚举
,就不会入侵编译结果。
TypeScript函数类型
函数类型比较简单,主要就是参数的约定以及返回值约定:
function fn(num:number,num2:number,...args:number[]):string{
return 'fn'
};
fn(1,2)
fn(1,2,3)
fn(1,2,3,4)
箭头函数:
const fn4=(a:number,b:number):string=>{
return 'lonjin'
}
可选参数和默认参数:
function fn2(a:number,b?:number,c:number=4,...agrs:number[]){
console.log(a,b,c,agrs)
}
fn2(1,2)//1 2 4 []
fn2(1,2,3)//1 2 3 []
fn2(1,2,3,4,5)//1 2 3 [ 4, 5 ]
TypeScript任意类型
很多场景下我们并不需要对类型有明确的约束,这时候就可以使用any
,any
虽好,可不要过度使用哦!
function fn(val:any){
console.log(val)
}
//可以传任意类型
fn(1)
fn('lonjin')
fn(true)
TypeScript隐式类型推断
隐式类型推断很好理解,如果我们在声明一个变量时候没有定义它的类型,ts
根据变量后的值进行类型推断:
let n=100;
//相当于
let n:number=100;
如果我们在声明时候没有赋值,则会被推断为any
:
let n;
// 相当于 let n:any;可以赋值任意类型数据
n=100;
n='lonjin';
n=true;
TypeScript类型断言
在实际场景中,我们可能会遇到以下情况:从接口中获取到一个数组,数组中存放的都是都是数字,我们需要取到某个值然后再进行一系列的计算:
//接口中返回的数组
let arr=[1,2,3,4,5];
let num=arr.find(n=>n>4);
//报错:ts并不确定num是不是一个number let num: number | undefined
let num2=num+1
这时候我们就可以使用类型断言进行处理,告诉ts
这确定是一个数字
let arr=[1,2,3,4,5];
let num =arr.find(n=>n>4);
let num2=num as number
let num3=num2+1
断言除了as
,还有另外一种书写方法:
let arr=[1,2,3,4,5];
let num =arr.find(n=>n>4);
let num3=<number>num+1
TypeScript接口
接口主要约束对象中的数据类型,基本使用如下:
interface People{
name:string,
age:number,
}
const people:People={
name:'lonjin',
age:18
}
很多情况下,我们可能确定不了是否会有某个值,这时候我们就需要使用可选参数了:
interface People{
name:string,
age:number,
sex?:boolean,
}
const people:People={
name:'lonjin',
age:18,
}
有时候我们根本不知道这个对象中要放什么,可以理解为动态场景,可以给对象任意添加一些数值:
interface People{
name:string,
[prop:string]:any
};
const people:People={
name:'lonjin',
};
people.say=function(){
console.log('hello')
};
people.home='beijing'
如果我们希望某些值设置为只读,可以在接口中加入修饰符readonly
:
interface People{
name:string,
age:number,
readonly hasCar:boolean,
}
const people:People={
name:'lonjin',
age:18,
hasCar:false
}
//报错:无法分配到 "hasCar" ,因为它是只读属性。
people.hasCar=true;
TypeScript类的基本使用
基本使用比较简单,我们直接看代码:
class People{
class People{
name:string;
age:number;
constructor(name:string,age:number){
this.name=name;
this.age=age;
}
say(message:string):void{
console.log(`my name is ${this.name},${message}`)
}
}
let tom=new People('tom',18)
类的修饰符
在类中,一共有三个修饰符:
-
pubilc
:公有成员,如果没有加修饰符,则默认为pubilc
。 -
private
:只能在成员方法内部访问 -
protected
:也不能在外部访问,和private
的区别就是,protected
是只允许在子类中访问成员
class People{
public name:string;
private age:number;
protected sex:boolean;
constructor(name:string,age:number){
this.name=name;
this.age=age;
this.sex=true;
}
say(message:string):void{
console.log(`my name is ${this.name},${message}`)
}
}
class Son extends People{
constructor(name:string,age:number){
super(name,age);
console.log(this.sex)//true
}
}
除了上面提到的,还可以设置只读属性readonly
。顾名思义,只允许读取,不允许修改。如果前面有修饰符,就跟在修饰符后面:
public name:string;
private age:number;
protected readonly sex:boolean;//只读属性
constructor(name:string,age:number){
this.name=name;
this.age=age;
this.sex=true;
}
say(message:string):void{
console.log(`my name is ${this.name},${message}`)
}
}
类与接口
- 如果类与类之间有共同特性,可以使用
类
来进行抽离,举个例子,人会吃东西,也会跑,车只会跑,但不会吃东西,所以我们可以把这些抽离出来:
interface Eat{
eat(food:string):void;
}
interface Run{
run(type:string):void;
}
class People implements Eat,Run{
eat(food:string):void{
console.log(`eat ${food}`)
};
run(type:string){
console.log(`people ${type}`)
}
}
class Car implements Run{
run(type:string):void{
console.log(`car is ${type}`)
}
}
抽象类
抽象类也用来约束子类当中某些成员,与接口不同的是,抽象类可以包含具体的实现,也可以只约束方法,具体例子如下:
//抽象类
abstract class Eat{
eat(food:string):void{
console.log(`eat ${food}`)
}
//约束People中必须有run方法
abstract run(type:number):void;
}
class People extends Eat{
run(type: number): void {
console.log(`run ${type}`)
}
}
let tom=new People();
tom.eat('foods');
tom.run(100)
泛型
泛型就是在定义函数、接口、或者类的时候没有具体定义类型,在使用时候才进行定义:
//比如我们写一个函数,函数中接收两个值,最终return一个数组回去,我们接收的参数可能是字符串,也可能是数字
function add<T>(n:T,m:T):T[]{
let a=[n,m];
return a;
}
let str=add<number>(1,2)
let num=add<string>('one','two')