前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >typescript基础篇(7):类型检查机制

typescript基础篇(7):类型检查机制

作者头像
一粒小麦
发布2020-08-28 15:33:51
1.6K0
发布2020-08-28 15:33:51
举报
文章被收录于专栏:一Li小麦

7. 类型检查机制

所谓类型检查机制,就是编程语言编译器在做类型检查时,所秉持的原则,以及表现出的行为。

7.1 类型推断

TS作为一门灵活的强类型语言:如果你声明一个变量,不一定都要做类型注解,ts会根据某些规则,自动推断出变量的类型。

先来看基础类型的推断:

代码语言:javascript
复制
let name1:string;
let name2 = ''; // 推断为string
let name // 推断为any

let num = 1 // 推断为number

let arr = [] // 推断为any[]
let arr2 = [1] //推断为 number[]
// 当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。
let arr3 = [1,null] // 推断为 number|null

在函数中也是如此:

代码语言:javascript
复制
// 自动推断入参和返回值都是number
const c = (x = 1) => x + 1

以上都是等号右边向等号左边的推断,也存在等号左边向右边的推断(上下文推断):

代码语言:javascript
复制
window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button) //<- 报错
}

window.onmousedown = function(mouseEvent: any) {
  console.log(mouseEvent.button) //<- 可以直接使用mousedown的专属属性。
}

有时候,你对你的代码有充足的自信,且想要推翻对ts的推论。这时候需要用到类型断言——回想鸭子模型,如果一个动物不符合鸭子的特征,那么开发者可以“断言”,让它被归类为鸭子。此处再复习下:

代码语言:javascript
复制
// 试改造如下代码
let foo = {}
foo.bar = 1 // <-报错

思路是定义一个接口,让它有bar这个属性:

代码语言:javascript
复制
interface Foo {
  bar: number
}
let foo = {} as Foo // <- 不报错
// foo.bar = 1

到这里为止,代码不报错,相当于使用类型断言绕过了代码推断。但这个写法并不好。如果我不写foo.bar=1,这里的检查就漏过去了。建议是直接注解foo:

代码语言:javascript
复制
let foo: Foo = {
  bar: 1,
}

所以,不要滥用断言

7.2 类型兼容性

当一个类型Y可以被赋值给另一个类型Y时,我们就可以说,X兼容Y。

代码语言:javascript
复制
X兼容Y:X(目标类型)= Y(源类型)

举个例子,当tsconfig.json"strictNullChecks": false时,以下操作时被允许的:

代码语言:javascript
复制
let s: string = "a"
s = null

为什么呢?因为在typescript中,null被默认为字符串的子类型。因此可以说:字符串类型兼容null类型。

7.2.1 对象的兼容性

再来看一个问题:

代码语言:javascript
复制
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 // <-报错:类型 "X" 中缺少属性 "c",但类型 "Y" 中需要该属性。

让我们重新温故下鸭子模型:

" 当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。 "——在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。

只要Y接口具备X接口的所有必要成员,那么X就兼容Y(成员少的兼容成员多的)。

7.2.2 函数的兼容性

对于函数的兼容,包含以下条件:

1.参数的个数

代码语言:javascript
复制
// 声明一个Handler类型
type Handler = (a: number, b: number) => void
// 高阶函数
const hoc = (handler: Handler) => handler

// 1.参数个数<=Handler参数
let handler1 = (a: number) => {}
hoc(handler1)

// 参数个数>Handler参数
let handler2 = (a: number, b: number, c: number) => {}
// hoc(handler2) //<-报错
代码语言:javascript
复制
// 可选参数和剩余参数
let a = (p1: number, p2: number) => {}
let b = (p1?: number, p2?: number) => {}
let c = (...args: number[]) => {}

// 固定参数:可以兼容可选参数和不定参数
a = b
a = c

// 可选参数
// b = a // <- 报错,可配置tsconfig去除
// b = c // <- 报错,可配置tsconfig去除

// 不定参数
c = a
c = b

如果通过接口来定义两个函数的入参:

代码语言:javascript
复制
interface Point3D {
  x: number
  y: number
  z: number
}

interface Point2D {
  x: number
  y: number
}

let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}

p3d = p2d
p2d = p3d // <-报错

在此,对于定义了接口类型的函数参数个数多的兼容参数个数少的

1.参数类型匹配

如果两个函数参数类型无法对应,二者无法兼容

代码语言:javascript
复制
// 声明一个Handler类型
type Handler = (a: number, b: number) => void
// 高阶函数
const hoc = (handler: Handler) => handler

let handler3 = (a: string) => {}
hoc(handler3) // <-报错:handler3 和 Handler不兼容

1.返回值类型:

目标函数的返回值类型必须与源函数的返回值相同,或为其子类型。

代码语言:javascript
复制
let f = () => ({ name: "djtao" })
let g = () => ({ name: "djtao", job: "coder" })

f = g
g = f //<-报错

1.函数重载

具体实现的函数中,参数不得多于重载签名,参数类型,返回类型只能是重载签名的类型。

代码语言:javascript
复制
// 重载列表
function overload(a: number, b: number): number
function overload(a: string, b: string): string

function overload(a: any, b: any): any {}
7.2.3 枚举类型的兼容性

枚举和number可以相互兼容。

代码语言:javascript
复制
enum Fruit {
  Apple,
  Banana,
}

let fruit: Fruit.Apple = 1
let n_fruit: number = Fruit.Apple

但是枚举类型之间完全不兼容

代码语言:javascript
复制
let color: Color.Red = Fruit.Apple // <-报错
7.2.4 类的兼容性

现有两个类A和B:

代码语言:javascript
复制
class A {
  id: number = 1
  constructor(p: number, q: number) {}
}

class B {
  static s = 1
  id: number = 2
  constructor(p: number) {}
}

let aa = new A(1, 2)
let bb = new B(2)

aa = bb
bb = aa

比较两个类是否兼容:静态成员和构造函数是不参与比较的。在此基础上,如果拥有相同的实例成员(在上面例子中,相同实例成员为id),那么二者可以相互兼容。(此情形包括父类和子类之间)

两个类,公共成员一致,如果有私有成员(private):只需要考虑三种情况:

•一个有,一个没有:没有的兼容有的•子类有特殊的私有属性:父类兼容子类。•其它情况:相互不兼容

7.2.5 泛型兼容性

对于泛型接口,如果不定义任何成员,哪怕具体传参不同,都是相互兼容:

代码语言:javascript
复制
interface Empty<T> {}

let obj1: Empty<number> = {}
let obj2: Empty<string> = {}

obj1 = obj2
obj2 = obj1

但如果我在Empty中定义了一个成员:

代码语言:javascript
复制
interface Empty<T> {
  value:<T>
}

二者互不兼容。也就是说:当成员类型被定义了,泛型接口之间就不能兼容

对于泛型函数:如果两个泛型函数的定义相同,没有指定参数类型。那么两个函数之间是完全兼容的。

代码语言:javascript
复制
let log1 = <T>(x: T): T => {
  console.log("x")
  return x
}

let log2 = <U>(x: U): U => {
  console.log("y")
  return x
}

log1 = log2
log2 = log1
7.2.6 小结

类型兼容性的用例非常多且繁杂,但是都是基于鸭子模型。为此,总结的规律是:

•结构之间:成员少的兼容成员多的•函数之间:参数多的兼容成员少的

7.3 类型保护

先看个例子:

我们用枚举类型实现一个语言选择方法,逻辑是判断是否强类型,是则执行helloJava并返回Java,否则执行helloJavascript并返回JavaScript。目前已完成的代码如下:

代码语言:javascript
复制
enum Type {
  Strong,
  Weak,
}

class Java {
  helloJava() {
    console.log("hello java")
  }
}

class JavaScript {
  helloJavaScript() {
    console.log("hello JavaScript")
  }
}

const getLanguage = (type: Type) => {
  let lang = type === Type.Strong ? new Java() : new JavaScript()
  return lang
}

getLanguage(Type.Strong)

接下来完成核心的getLanguage:

代码语言:javascript
复制
const getLanguage = (type: Type) => {
  let lang = type === Type.Strong ? new Java() : new JavaScript()
    // 报错:类型“Java | JavaScript”上不存在属性...
  if (lang.helloJava) {
    lang.helloJava()
  } else {
    lang.helloJavaScript()
  }
  return lang
}

从报错信息看,ts是把lang作为了一种联合类型,以至于访问helloJava出错。所以我们使用类型断言:

代码语言:javascript
复制
const getLanguage = (type: Type) => {
  let lang = type === Type.Strong ? new Java() : new JavaScript()
  // 注意,不能写`(lang as Java).helloJava`,3.7之后会校验
  if (!!(lang as Java).helloJava) {
    ;(lang as Java).helloJava()
  } else {
    ;(lang as JavaScript).helloJavaScript()
  }

  return lang
}

getLanguage(Type.Strong)

这段lang as的断言写的有些非主流了,为了正常调用,不得不多次进行断言。而类型保护机制就是为了解决这类问题而诞生的。ts能够在特定的区块中保证变量属于某种确定的类型,你可以在此区块中放心使用此类型的使用和方法。

以下阐述四种创建此区块的方法。

7.3.1 instanceof

Instanceof可以判断一个对象是否属于某种类型的实例。

代码语言:javascript
复制
const getLanguage = (type: Type) => {
  let lang = type === Type.Strong ? new Java() : new JavaScript()
  // 1.instanceof
  if (lang instanceof Java) {
    lang.helloJava()
  } else {
    lang.helloJavaScript()
  }
  return lang
}
7.3.2 in关键字

逻辑和instanceof类似,判断某个属性/方法是否存在。

代码语言:javascript
复制
const getLanguage = (type: Type) => {
  let lang = type === Type.Strong ? new Java() : new JavaScript()
  // 2.in关键字
  if ("helloJava" in lang) {
    lang.helloJava()
  } else {
    lang.helloJavaScript()
  }
  return lang
}
7.3.3 typeof
代码语言:javascript
复制
    // 3. typeof
  if (typeof x == "string") {
    // x可以直接获得字符串方法
  } 
7.3.4 类型保护函数
代码语言:javascript
复制
// 4.类型保护函数
function isJava(lang: Java | JavaScript): lang is Java {
  return (lang as Java).helloJava !== undefined
}

const getLanguage = (type: Type) => {
  let lang = type === Type.Strong ? new Java() : new JavaScript()
  if (isJava(lang)) {
    lang.helloJava()
  } else {
    lang.helloJavaScript()
  }
  return lang
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 7. 类型检查机制
    • 7.1 类型推断
      • 7.2 类型兼容性
        • 7.2.1 对象的兼容性
        • 7.2.2 函数的兼容性
        • 7.2.3 枚举类型的兼容性
        • 7.2.4 类的兼容性
        • 7.2.5 泛型兼容性
        • 7.2.6 小结
      • 7.3 类型保护
        • 7.3.1 instanceof
        • 7.3.2 in关键字
        • 7.3.3 typeof
        • 7.3.4 类型保护函数
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档