探究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类型检查机制,面试题等,如果你感兴趣那就点个赞👍和关注吧。

相关推荐
wangruofeng4 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
怕浪猫4 小时前
这个十几个agent 协作项目值得关注
人工智能·面试·github
wangruofeng4 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈7 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技8 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569158 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马10 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng11 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding12 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js
鶴哥只手遮天12 小时前
《Electron 45.2.3 内核解剖》第 4 章 补丁体系:380 个补丁改了什么
前端·electron