细说Typescript类型检查机制
上一篇文章我介绍了Typescript的基础知识,回顾一下,有基础数据类型、数组、函数、类、接口、泛型等,本节内容将述说一下Typescript为方便我们开发提供了一些类型检查机制。
类型检查机制
类型检查机制: Typescript编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。
作用:辅助开发,提高开发效率。主要有以下三点:
- 类型推断
- 类型兼容性
- 类型保护
1、类型推断
不需要指定变量的类型(函数的返回值类型), Typescript可以根据某些规则自动地为其推断出一个类型。
- 基础类型推断。是ts中比较常见的类型推断。
// 没有设置类型时,TS会根据赋值的类型推断类型
let a1; // 等价于 let a1: any
let a = 1; // 等价于 let a: number
let b = [1]; // 等价于 let b: number[]
let c = [1, null]; // 等价于 let c: number[]
let c1 = (x = 1) => {} // 等价于 let c1: (x?: number) => void
let d = (x = 1) => x + 1; // 等价于 let d: (x?: number) => number
- 最佳通用类型推断
// 断言好处:改造旧代码会很有效,但避免滥用,需对上下文环境有充足的预判,否则会带来安全隐患
interface Foo {
bar: number
}
// let foo = {}; // 报错
// let foo = {} as Foo; // 注意:不可滥用断言,若去掉bar属性,foo对象就没有按照接口的严格约定返回,就会遗漏
// foo.bar = 1;
let foo: Foo = { // 最佳通用类型推断
bar: 1
};
- 总结:TS的类型推断可以为我们提供一些重要的辅助信息,但是需要合理使用
2、类型兼容性
当一个类型 y 可以被赋值给另一个类型 x 时,我们就可以说类型 x 兼容类型 y x 兼容 y : x (目标类型)= y(源类型);
简单理解:
接口之间兼容:成员少的兼容成员多的
函数之间兼容:参数多的兼容参数少的
2.1 类型兼容
// x 兼容 y : x (目标类型) = y(源类型)
let s: string = 'a'
s = null; // 报错
上述的报错,在tsconfig.json 设置 strictNullChecks:false即可
2.2 接口兼容
接口之间兼容:成员少的会兼容成员多的
interface XX {
a: any;
b: any;
}
interface YY {
a: any;
b: any;
c: any;
}
let xx: XX = {a:1, b:2}
let yy: YY = {a:1, b:2, c:3} xx = yy;
// yy = xx; // 报错。注意:源类型必须要具备目标类型的必要属性。xx可以兼容yy类型,反之不行
2.3 函数兼容
判断两个函数是否兼容,一般会发生在相互赋值的场景下。
函数之间兼容:参数多的兼容参数少的
type Handler = (a: number, b: number) => void; // Handler 为目标类型,传入的参数为源类型
function hocf(handler: Handler){
return handler
}
// 目标函数要兼容源函数需要同时满足以下三点:
// 1) 参数个数要求: 目标函数的个数要多于源函数参数的个数
// 固定参数个数
let handler1 = (a: number) => {};
hocf(handler1);
// let handler2 = (a: number, b: number, c: number) => {};
// hocf(handler2); // 报错。目标函数的个数要多于源函数参数的个数 // 不固定参数:可选参数和剩余参数: 需坚持三原则
let l = (p1: number, p2: number) => {}
let m = (p1?: number, p2?: number) => {}
let n = (...args: number[]) => {}
// - 1.固定参数可以兼容可选参数和剩余参数
l = m;
l = n;
// - 2.可选参数不兼容固定参数和剩余参数,但设置 tsconfig strictFunctionTypes:false 属性,可预防报错
m = n // 报错
m = n // 报错
// - 3.剩余参数可以兼容固定参数和可选参数
n = l
n = m // 2) 参数类型要求
// 基础类型接口
let handler3 = (a: string) => {};
// hocf(handler3); // 报错。类型不兼容 // 对象类型接口:成员多的兼容成员少的,与接口成员兼容相反,可以看成参数,参数多的兼容参数少的
interface Point3D {
x: number;
y: number;
z: number;
}
interface Point2D {
x: number;
y: number;
}
// 参数个数相同,参数类型相同Object
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}
p3d = p2d
p2d = p3d // 报错。设置 tsconfig strictFunctionTypes:false 属性,可预防报错
// 函数参数之间可以相互赋值的情况被称为 参数双向协变。作用:可以把精确类型 赋值给 不精确类型,这样就不需要把不精确的类型断言成精确类型 // 3) 返回值类型。ts要求我们目标函数的返回值类型必须要和源函数返回值类型一致或者是其子类型
// 成员少的兼容成员多的
let o = () => ({name: "Alice"});
let p = () => ({name: "Alice", location: 'Beijing'});
o = p;
// p = o; // 报错
2.4 函数重载兼容
// 在重载中,目标函数的参数要多于源函数的参数
function overload(a: number, b: number): number // 列表中的函数:目标函数
function overload(a: string, b: string): string // 列表中的函数:目标函数
function overload(a: any, b: any): any {} // 具体实现:源函数
// function overload(a: any, b: any, c: any): any {} // 报错。在重载中,目标函数的参数要多于源函数的参数
// function overload(a: any, b: any) {} // 报错。返回值参数类型不兼容
2.5 枚举兼容
enum Fruit { Apple, Banana }
enum Color { Red, Yellow }
// 枚举类型和数字类型是完全可以互相兼容和赋值的
let fruit: Fruit.Apple = 3;
let no: number = Fruit.Apple;
// 枚举类型之间是不兼容的
// let color: Color.Red = Fruit.Apple; // 报错
2.6 类兼容
类兼容和接口兼容相似,都是只比较结构;
1. 构造函数constructor、和静态成员static是不作为比较的;
2. 如果两个类具有相同的实例成员,这两个类之间也是兼容的;
3. 类中有私有成员 private,两个类是不兼容的,只有父类和子类是兼容的;
class A {
constructor(p: number, q: number){}
id: number = 1
// private name: string = ''// 3. 报错
}
class B {
static s = 1;
constructor(p: number){}
id: number = 2
// private name: string = '' // 3. 报错
} let aa = new A(1,2)
let bb = new B(1)
// 2. 都具有id属性,所以相互兼容
aa = bb;
bb = aa;
// 3. 父类和子类是兼容的
class An extends A { }
let an = new An(1,2)
aa = an;
an = aa;
2.7 泛型兼容
// - 不会报错
interface Empty<T>{ }
let obj1: Empty<number> = { }
let obj2: Empty<number> = { }
obj1 = obj2;
// - 会报错 只有接口参数T被接口使用的时候,才会影响泛型的兼容性
// interface Empty<T>{
// value: T
// }
// let obj1: Empty<number> = {}
// let obj2: Empty<number> = {}
// obj1 = obj2; // 2. 泛型函数
let loog1 = <T>(x: T): T => {
console.log('x')
return x
}
let loog2 = <U>(y: U): U => {
console.log('y')
return y
}
loog1 = loog2; // 两个泛型函数的定义相同,并没有指定类型参数,泛型函数也是兼容的
2.8 总结
1、考虑类型兼容是因为ts允许我们把一些类型不同的变量相互赋值,虽然在某种程度上讲,会产生不可靠的行为,但这个特性却增加了语言的灵活性
2、类型兼容的使用场景:广泛存在接口、函数、类中 演示
3、类型保护
定义: Typescript能够在特定的区块(类型保护区块)中保证变量属于某种确定的类型。 可以在此区块中放心地引用此类型的属性,或者调用此类型的方法。
换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。
enum Type { Strong, Week }
class Java {
helloJava(){
console.log('Hello Java')
}
java: any
}
class JavaScript {
helloJavaScript(){
console.log('Hello JavaScript')
}
javascript: any
}
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()
}
// console.log(lang, 'lang')
return lang;
}
上述的解决方案显然不是最佳方案,有以下四种方案可以解决类型保护:
3.1 方案1: instanceof
function getLanguage(type: Type, x: string|number){
let lang = type === Type.Strong ? new Java() : new JavaScript();
if(lang instanceof Java){
lang.helloJava()
} else {
lang.helloJavaScript()
}
// console.log(lang, 'lang')
return lang;
}
3.2 方案2: in
function getLanguage(type: Type, x: string|number){
let lang = type === Type.Strong ? new Java() : new JavaScript();
if('java' in lang){
lang.helloJava()
} else {
lang.helloJavaScript()
}
// console.log(lang, 'lang')
return lang;
}
3.3 方案3: typeof
function getLanguage(type: Type, x: string|number){
let lang = type === Type.Strong ? new Java() : new JavaScript();
// x也是联合类型,typeof类型保护,可以判断出基本类型。
if(typeof x === 'string'){
x.length
} else {
x.toFixed
}
// console.log(lang, 'lang')
return lang;
}
3.4 方案4: 类型保护函数
function getLanguage(type: Type, x: string|number){
let lang = type === Type.Strong ? new Java() : new JavaScript();
if(isJava(lang)){
lang.helloJava()
}else{
lang.helloJavaScript()
} // console.log(lang, 'lang')
return lang;
}
定义一个类型保护函数:
// 类型保护函数,可以传递任何值给 isJava 函数,用来判断它是不是 Java。isJava 函数与普通函数的最大区别是,该函数的返回类型是 lang is Java,这就是 "类型谓词"。
function isJava(lang: Java | JavaScript): lang is Java{
// 类型谓词可参考:https://segmentfault.com/a/1190000022883470
return (lang as Java).helloJava !== undefined // 返回结果是 true,那么当前判断的 lang 变量值的类型是 Java 类型。
}
类型保护作用:
1. 可以提前对类型做预判
2. 返回类型谓词,如 lang is Java;
3. 包含可以准确确定给定变量类型的逻辑语句,如 (lang as Java).helloJava !== undefined。
3.5 总结
不同的判断方法有不同的使用场景:
typeof:判断一个变量的类型(多用于基本类型);
instanceof:判断一个实例是否属于某个类;
in:判断一个属性是否属于某个对象;
类型保护函数:某些判断可能不是一条语句能够搞定的,需要更多复杂的逻辑,适合封装到一个函数内。
结合ts类型检查机制再配合vscode 自动补全和自动修复功能,能够极大的提高我们的开发效率!
4、结尾
截止本节,Typescript的基础知识已经介绍差不多了,后期如有时间会继续更新TS的进阶高级部分,感谢您的阅读~
细说Typescript类型检查机制的更多相关文章
- TypeScript类型检查机制
类型推断 指不需要指定变量的类型,TS编译器可以根据某些规则自动推断出类型. 什么时候会有类型推断? 声明变量时没有指定类型 函数默认参数 函数返回值 ...... let a; // 这时自动推断为 ...
- 【长文详解】TypeScript、Babel、webpack以及IDE对TS的类型检查
只要接触过ts的前端同学都能回答出ts是js超集,它具备静态类型分析,能够根据类型在静态代码的解析过程中对ts代码进行类型检查,从而在保证类型的一致性.那,现在让你对你的webpack项目(其实任意类 ...
- TypeScript 类型推导及类型兼容性
类型推导就是在没有明确指出类型的地方,TypeScript编译器会自己去推测出当前变量的类型. 例如下面的例子: let a = 1; 我们并没有明确指明a的类型,所以编译器通过结果反向推断变量a的类 ...
- 类型检查和鸭子类型 Duck typing in computer programming is an application of the duck test 鸭子测试 鸭子类型 指示编译器将类的类型检查安排在运行时而不是编译时 type checking can be specified to occur at run time rather than compile time.
Go所提供的面向对象功能十分简洁,但却兼具了类型检查和鸭子类型两者的有点,这是何等优秀的设计啊! Duck typing in computer programming is an applicati ...
- CesiumJS新增官方TypeScript类型定义
Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 在当前的1.70版本中,CesiumJS现在附带了正式的Type ...
- 解析 Linux 内核可装载模块的版本检查机制
转自:http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmodules/ 为保持 Linux 内核的稳定与可持续发展,内核在发展过程中引进了可 ...
- 编译期类型检查 in ClojureScript
前言 话说"动态类型一时爽,代码重构火葬场",虽然有很多不同的意见(请参考),但我们看到势头强劲的TypeScript和Flow.js,也能感知到静态类型在某程度上能帮助我们写出 ...
- 享受Python和PHP动态类型检查语言的快感
前言 写这文章的时候特地查了资料,以确保我没有说错关于Python和PHP的类型机制. 所以这里放一张图,关于强弱类型与动态/静态类型检查的区分 从分类上看,PHP属于弱类型语言,而Python属于强 ...
- 介绍几款 Python 类型检查工具
近日,微软在 Github 上开源了一个 Python 静态类型检查工具:pyright ,引起了社区内的多方关注. 微软在开源项目上的参与力度是越来越大了,不说收购 Github 这种大的战略野心, ...
随机推荐
- Redis主从复制那点事
我们在 Redis持久化机制你学会了吗?学习了AOF和RDB,如果Redis宕机,他们分别通过回放日志和重新读入RDB文件的方式恢复数据,从而提高可靠性.我们今天来想这么一个问题,假如我们 ...
- java try_catch 分析
1.若一段代码前有异常抛出,并且这个异常没有被捕获,这段代码将产生编译时错误「无法访问的语句」.如 public class try_catch_Demo { public static void m ...
- java构造器级简单内存分析
java构造器的使用(基础篇) 构造方法也叫构造器,是创建对象时执行的特殊方法,一般用于初始化新对象的属性. 基本定义语法: 访问控制符 构造方法名([参数列表]){ 方法体 } 注:"访问 ...
- 还不了解一下 Java 8 Predicate 函数接口
同学你好,这里有一份你的未读代码,等你查收. 这篇文章属于 Java 8 教程(LTS)系列教程,点击阅读更多相关文章. Predicate 函数接口同之前介绍的 Function 接口一样,是一个函 ...
- FastAPI实战:简易MockServe
Mock 我个人理解就是为了伪造返回结果的东西,MockServe通常说的就是接口未开放完成但是现在需要调用,所以无论是通过转发还是直接访问这个url,其结果基本就是自己定义的 当然做仔细点就是 给个 ...
- Spring Cloud分区发布实践(5)--定制ServiceInstanceListSupplier
现在我们简单地来定制二个 ServiceInstanceListSupplier, 都是zone-preference的变种. 为了方便, 我重新调整了一下项目的结构, 把一些公用的类移动到hello ...
- 一文彻底搞清 Gradle 依赖【转】
来源:曾是放牛娃 www.jianshu.com/p/59fd653a54d2 转自:https://mp.weixin.qq.com/s?__biz=MzA3MDMyMjkzNg==&mid ...
- AlarmManager定时提醒的那些坑
https://blog.csdn.net/zackratos/article/details/53243595 https://blog.csdn.net/bingshushu/article/de ...
- 凯撒密码(Caesar cipher) 详解
------------恢复内容开始------------ 最近训练CTF的时候,发现密码学这块的知识不太系统,所以自己接下来会陆陆续续整理出来 就先从古典密码中的凯撒密码说起吧 凯撒密码内容比较简 ...
- TypeScript学习笔记(三)泛型、模块化和命名空间
目录 一.泛型 1. 泛型函数 2. 泛型类 3. 泛型接口 写法一 写法二 两种写法的区别 二.模块化 1. export写法一 2. export写法二 3. 为引入的方法或变量起别名 4. ex ...