探究Typescript中奇怪的赋值操作

探究Typescript中奇怪的赋值操作

前言

本文主要讨论在typescript中一些奇怪的赋值语句,探索其背后原因,更深入的了解typescript作为一个结构化系统的特性。

我们先看这样一个例子:

typescript 复制代码
// 定义一个Cat类
class Cat {
  name!: string
  age!: number
​
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}
// 定义一个Dog接口类型
interface Dog {
  name: string
  age: number
}
​
let dog: Dog = {
  name: 'wuyue',
  age: 3
}
​
let cat: Cat
cat = new Cat('qiqi', 2)
cat = dog // is ok 
dog = cat // is ok 
cat = { name: 'jack', age: 12 } // is ok
  • 在上面的例子中,我们定义了一个Cat类,一个Dog接口类型, Cat类的实例对象与Dog类型的变量相互赋值,可以通过Typescript的类型检查,而将{ name: 'jack', age: 12 }分配给一个Cat类型的变量也是可以的。
  • 这是结构化类型系统的特性,Typescript就采用了这种结构类型。结构化类型系统的思想在于,名称不重要,重要的是它们是否具有类型的相同成员,如果是则是兼容的。 结构化类型系统也叫鸭子类型, 如果你看到一只鸟走起来像鸭子,游泳像鸭子,叫得也像鸭子,那么这只鸟就是鸭子
  • 上个例子中cat变量,dog变量,以及{ name: 'jack', age: 12 } 字面量他们都具有相同的结构,所以Typescript在类型检查时认为他们之间的赋值操作是合法的。

我们再来看另一个例子

typescript 复制代码
// 定义一个Cat类
class Cat {
  name!: string
  age!: number
​
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}
// 定义一个Dog接口类型
interface Dog {
  name: string
  age: number
}
let dog: Dog = {
  name: 'wuyue',
  age: 3
}
​
function getCat(cat: Cat) {}
​
getCat(new Cat('cc', 2)) // ok
getCat(dog) // ok
getCat({ name: 'jack', age: 12 }) // ok
const obj = { name: 'jack', age: 12, sex: 1 } // ok
getCat(obj) // ? ok
  • 在这个例子中,我们定义的Cat类和Dog接口类型和上面的例子一致,紧接着我们定义了一个getCat的函数,接受一个Cat类型的形参。

  • 根据上一个例子中我们学到的结构化类型系统的知识,可以很快的判断出来 new Cat('cc', 2)dog{ name: 'jack', age: 12 } 都可以分配给Cat类型的变量,那么最后一个可以吗? 我们在 typescript Playground中测试发现是可以的,typescript对上面的代码都通过了类型检查,但是你可能还有困惑,obj明显多了一个sex属性,而sex属性在Cat类型中并不存在,Typescript为什么也对其通过了类型检查?先放下这个疑问,我们在看这么一行代码

    php 复制代码
    getCat({ name: 'jack', age: 12, sex: 1 }) 
  • 这行代码竟然报错了,事情变得更加奇怪了。

再来看一个例子:

是不是很奇怪,同样的值,直接赋值/传参会报错,如果先定义一个变量然后将这个变量进行赋值/传参就不会报错。

通过查阅各种官方资料发现这么一个结论: 在typescript类型系统中,对对象进行类型检查时定义的方式会对结果产生影响,在创建一个对象字面量并直接分配给具有某个类型的变量时,typescript会最严谨的验证对象, 进行严格的属性检查(ECP),而当将对象变量分配给另一个变量,对这个变量进行类型检查时,typescript会进行兼容性判断,如果兼容则通过检查,如果不兼容则报错。

而这个兼容性判断是如何判断的呢? 请记住下面这句话:

假如x要分配给y,在TypeScript中,X需要更具体,即X要有和Y相同的属性或者更多。 可分配性指的就是兼容性。

如: let v = { name: 'john'; age: 20 } 兼容{ name: string }, 类型兼容,通过检查。let v = {foo: 1, bar: 2} 兼容{foo: number} , 类型兼容,通过检查。而这个问题可以被看成是类型加宽。

对于上面的现象,有好处也有坏处,对此有人提出来typescript新加入一个精确类型的语法,引起了激烈的讨论:可以看下面这个链接。 #12936

结尾

在网上大多数的案例中,总是会出现这样的例子:

css 复制代码
interface Person {
  name: string
  age: number
}

let p: Person = {
  name: 'jack',
  age: 12,
}

我们似乎认为p变量一定要精确的满足于Person的定义,如果多加了属性那么就会报错,这是typescript的基石,ts就应该做这样的事,其实不然,typescript作为一个结构化系统,多加了属性并不会报错, "多加了属性那么就会报错" 的功能其实是在typescript中是一种linter校验的功能,而且它仅适用于对象字面量。所以有时候我们也会说Typescript是Javascript一个超级强大的linter工具

感谢阅读,很喜欢Typescript,后面想多写一些关于Typescript的文章,包括频繁引发令人困扰的Typescript类型检查机制,面试题等,如果你感兴趣那就点个赞👍和关注吧。

相关推荐
CodeSheep4 分钟前
JetBrains官宣,又一个IDE可以免费用了!
前端·后端·程序员
刘新明19896 分钟前
Frida辅助分析OLLVM虚假控制流程(下)
java·开发语言·前端
江城开朗的豌豆23 分钟前
小程序登录不迷路:一篇文章搞定用户身份验证
前端·javascript·微信小程序
aesthetician27 分钟前
React 19.2.0: 新特性与优化深度解析
前端·javascript·react.js
FIN666842 分钟前
射频技术领域的领航者,昂瑞微IPO即将上会审议
前端·人工智能·前端框架·信息与通信
U.2 SSD1 小时前
ECharts漏斗图示例
前端·javascript·echarts
江城开朗的豌豆1 小时前
我的小程序登录优化记:从短信验证到“一键获取”手机号
前端·javascript·微信小程序
excel1 小时前
Vue Mixin 全解析:概念、使用与源码
前端·javascript·vue.js
IT_陈寒1 小时前
Java性能优化:这5个Spring Boot隐藏技巧让你的应用提速40%
前端·人工智能·后端
聪明的笨猪猪1 小时前
Java Spring “MVC ”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试