类型检查机制:
- TS编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。
- 作用:辅助开发,提高开发效率
类型推断
不需要指定变量的类型(函数的返回值类型),TS可以根据某些规则自动的为其推断出一个类型
- 基础类型推断
let aa; //自动推断为any类型
let s=1; //推断为number类型
let ms=[]; //推断为以any类型为元素的数组类型
let sd=[1]; //推断为number类型的数组
//设置函数默认参数的时候
let c=(x=1)=>{} //x被推断为number类型
//函数返回值
let m=(y=1)=>y+1 //m函数返回值被推断为numer类型
- 最佳通用类型推断(当需要从多个类型中推断一个类型的时候,TS尽可能推断出兼容当前所有类型的通用类型)
let ba=[1,null]; //number和null不兼容,推断为number和null的联合类型
注意,这里如果在ts中开启 strictNullChecks:false ,这个时候number和null就兼容了,ba就会被推断为number类型的数组
- 上下文类型推断(以上的表达式都是从右向左的推断,根据表达式右侧的值来推断变量的类型;还有一种类型推断是从左到右的,这就是上下文类型推断,通常发生在事件处理中)
//TS会根据左侧的事件绑定来推断右侧的事件类型
window.onkeydown=(event)=>{
console.log(event)
}
类型断言
当TS的类型推断不符合我们的预期,并且我们明确知道是什么类型的时候,TS提供了一种方法,允许我们覆盖它的推论,这就是类型断言。
let foo={
}
foo.bar=1 ; //这个时候会报错,因为foo中没有bar属性
我们通过类型断言来解决这个问题
interface Foo{
bar:number
}
let foo={} as Foo;
foo.bar=1;
//如果我们不给foo添加bar属性,ts也是不会报错的,这就是类型断言的一些弊端,所以我们在开发中不要乱用类型断言
所以最好的还是在声明的时候就指定它的类型
interface Foo{
bar:number
}
let foo:Foo={
bar:1
}
类型兼容性
当一个类型Y可以被赋值给另一个类型X时,我们就可以说X兼容类型Y
X兼容Y:X(目标类型)= Y(源类型)
接口的兼容(少的兼容多的)
interface X{
a:any;
b:any;
}
interface Y{
a:any;
b:any;
c:any;
}
let x:X={a:1,b:2}
let y:Y={a:1,b:2,c:3}
x=y;
y=x; //Property 'c' is missing in type 'X' but required in type 'Y'.
//源类型必须具备目标类型的必要属性,即使有额外属性也没有什么问题
函数的兼容
下面我们先定义一个Handler函数类型,然后定义一个hof函数,以Handler类型为参数。当我们给hof传入参数的时候,就会判断参数是否和Handler类型兼容,这里Handler就是目标类型,传入的参数就是源类型。如果要目标函数兼容源函数,要同时满足三个条件。
type Handler =(a:number,b:number)=>void;
function hof(hander:Handler){
return hander
}
1、参数个数,目标函数的参数个数一定要多于源函数的参数个数(多的兼容少的)
let hander1=(a:number)=>{}
hof(hander1)
let hander2=(a:number,b:number,c:string)=>{}
hof(hander2) =>//不兼容 ,类型“(a: number, b: number, c: string) => void”的参数不能赋给类型“Handler”的参数。
可选参数和剩余参数的时候会遵循另外的原则
- 固定参数兼容可选参数和剩余参数
- 可选参数不兼容固定参数和剩余参数(我们可以设置strictFunctionTypes:false选项来实现两者之间的兼容)
- 剩余参数可以兼容固定参数和可选参数
2、参数类型必须匹配
let hander3=(s:string)=>{}
hof(hander3) //报错,类型不兼容
3、返回值类型(目标函数的返回值类型必须与原函数返回值类型相同或者为其子类型)
let f=()=>({name:'alice'});
let g=()=>({name:'hs',location:"chengdu"});
f=g;
g=f //不兼容
枚举类型的兼容
enum GR{APPLE,BANNE}
enum COLO{RED,BKI};
let nos:number=GR.APPLE; //数字和枚举可以相互兼容
let caa:COLO.RED=GR.APPLE; //报错,枚举之间是完全不兼容的
类的兼容性(和接口相似,只比较结构)
注意:静态成员和构造函数不参与比较
如果两个类具有相同的实例成员,那么他们的实例就可以相互兼容
class A{
constructor(p:number,q:number){}
id:number=1
}
class B{
static s=1;
constructor (p:number){}
id:number
}
let sa=new A(1,2)
let sb=new B(1);
sb=sa;
sa=sb;
泛型的兼容
interface Em<T>{
}
let obj1:Em<number>={}
let obj2:Em<string>={}
obj1=obj2; //不报错
interface Em<T>{
value:T
}
let obj1:Em<number>={}
let obj2:Em<string>={}
obj1=obj2; //报错
在两个泛型参数类型不相同时,只有在泛型参数使用时才影响;两个泛型函数的定义相同,没有指定具体的参数时,是完全兼容的。
兼容性大概就是这些,我们总结两句好记得口诀:
结构之间的兼容:成员少的兼容成员多的
函数之间的兼容:参数多的兼容参数少的
类型保护
// ts的类型保护机制
enum Type {Strong,Week};
class Java{
helloJava(){
console.log('hello java');
}
java:any;
}
class JavaScript{
helloJavaScript(){
console.log('hello javascript');
}
javascript:any
}
// ts不能判断在程序运行的时候到底会传入哪一种参数,所以要在每一处都加上类型断言
function getLanguage(type:Type,x:string|number){
let lang=type===Type.Strong?new Java():new JavaScript();
if((lang as Java).helloJava){
(lang as Java).helloJava();
}else{
(lang as JavaScript).helloJavaScript();
}
return lang;
}
上面的代码到处都是类型断言,可读性很差。所以TS提供了类型保护机制,可以提前对类型做出预判。
什么是类型保护
- TS能够在特定的区块中保证变量属于某种确定的类型
- 可以在此区块中放心的引用此类型的属性,或者调用此类型的方法
下面我们就来介绍四种创建这种区块的方法:
- instanceof ,可以判断某个实例是不是属于某个类
if (lang instanceof Java){
lang.helloJava();
}else{
lang.helloJavaScript();
}
- in关键字,可以判断某个属性是否属于某个对象
if('java' in lang){
lang.helloJava();
}else{
lang.helloJavaScript();
}
- typeof
if(typeof x==='string'){
x.length;
}else{
x.toFixed()
}
- 通过类型保护函数
function isJavs(lang:Java|JavaScript):lang is Java{
return (lang as Java).helloJava !==undefined
}
if(isJavs(lang)){
lang.helloJava()
}else{
lang.helloJavaScript()
}