之前在技术需求中曾调研了基于 TypeScript 的数据校验方案,其中调研了一个叫 Deepkit 的第三方库,可以将 TypeScript 的类型信息保留到运行时进行消费。
传统开发上,Javascript 基本没有提供任何类型保护,所有的类型错误都需要在运行时才能发现,而TypeScript 为开发者提供了一套静态类型检查的方案,它提倡开发者在源码中主动声明类型信息,并与对应的变量和操作相匹配,并在编译阶段进行检查,类型相关的错误在编译时就暴露出来,一方面使代码更规范了,一方面也极大程度地规避了许多代码错误,提高了代码的健壮性。
TypeScirpt 拥有完备的类型系统。但很可惜,它在这方面的能力在运行时几乎完全不存在。TypeScript Compiler在编译源码时会删除类型信息,不对运行时造成任何开销。
但其实在许多场景下,运行时的类型信息都是极具价值的!
为什么我们需要运行时的类型信息呢?让我们看看下面两个场景
数据校验并不是局限于传统前端所关注的表单校验,需要数据校验的场景数不胜数,比如:
序列化是将数据类型转换为适合传输或存储的格式的过程。反序列化是撤消此操作的过程,这个过程需要保证是无损的。对于前端开发者来说,接触的最多的应该就是 JSON.parse()
和 JSON.stringify()
这两个方法。在简单场景下,用这两个方法做序列化和反序列化可能没有问题,但是在复杂场景中就不一定了,因为这两个方法并不能保证数据是无损的。
例如下面这个场景
const date = new Date();
const dateString = JSON.stringify(date);//"2022-11-02T17:49:03.240Z"
const dateJson = JSON.parse(dateString);//"2022-11-02T17:49:03.240Z"
对于日期类型的数据,先用 JSON.stringify(date) 将其序列化成了适合传输的格式,再用JSON.parse(dateString) 反序列化,发现日期这个类型在过程中已经丢失,最后反序列化的结果为一个字符串,这显然是不符合预期的。因此,在序列化和反序列化的过程中,类型信息也十分重要。
而 DeepKit 使将 TypeScript 类型保留到运行时成为现实。
官方文档站:https://deepkit.io/
使用 DeepKit 需要安装两个包:
package.json
的devDependencies
中,因为这个类型编译器只需要编译阶段使用。npm install --save @deepkit/type
npm install --save-dev @deepkit/type-compiler
然后需要在 tsconfig.json
中配置 "reflection": true
。如果需要使用装饰器,还需要加入"experimentalDecorators": true
参数
// tsconfig.json
{
"compilerOptions":{
"module":"CommonJS",
"target":"es6",
"moduleResolution":"node",
"experimentalDecorators":true
},
"reflection":true,
}
DeepKit 定义了两种用于描述运行时的类型信息的数据结构,分别是类型对象和反射类。
使用 typeOf 方法可以快速获取某个类型对应的类型对象。
import { typeOf } from '@deepkit/type';
type Title<T> = T extends true ? string : number;
typeOf<Title<true>>();
//Type {kind: 5, typeName: 'Title', typeArguments: [{kind: 7}]}
从上面的例子中,我们可以看到一个类型对象的基本数据结构(当然,这还不是它的全貌)。详细的类型对象定义:https://github.com/deepkit/deepkit-framework/blob/feature/autotype/packages/type/src/reflection/type.ts#L21-L452
enum ReflectionKind {
never, //0
any, //1
unknown, //2
void, //3
object, //4
string, //5
number, //6
boolean, //7
symbol, //8
bigint, //9
null, //10
undefined, //11
//... and even more
}
反射类多用于 类/接口/对象类型等等比较复杂的场景
import { ReflectionClass } from '@deepkit/type';
interface User {
id: number;
username: string;
}
const reflection = ReflectionClass.from<User>();
reflection.getProperty('id'); //ReflectionProperty,记录id类型信息
reflection.getProperty('id').name; //'id'
reflection.getProperty('id').type; //{kind: ReflectionKind.number}
reflection.getProperty('id').isOptional(); //false
reflection.removeProperty('id');
reflection.getProperty('id');//Error: No property id found in User
对于复杂场景,我们可以通过 ReflectionClass.from 方法得到类型对应的放射类实例 ReflectionClass ,通过调用ReflectionClass中的方法可以获取更深层次的类型信息,也可以对类型信息做一些操作。
需要数据验证的场景数不胜数,接口参数校验,数据库实现等都高度依赖数据校验,以此保证数据的安全性。
DeepKit 提供了is和validate两个函数,用于校验一个值是否符合类型定义。
interface People {
name: string
age: number,
info?: {
address?: string,
phone: number
}
}
const peopleA = {
name: 'Jack',
age: 20,
}
const peopleB = {
name: 'Peter',
age: 18,
info: {}
}
is<People>(peopleA)//true
is<People>(peopleB)//false
is 函数接收类型信息,并对参数中的数据进行校验,返回一个布尔值。如上面的例子,定义了一个 People 的 interface,并对 peopleA 和 peopleB 两个数据进行校验,可以看出 peopleA 是符合 People 的 定义的,所以返回is<People>(peopleA)
会返回 true 。peopleB 中的 info 属性缺少了必填的 phone 字段,因此is<People>(peopleB)
会返回 false 。
validate<People>(peopleA)//[]
validate<People>(peopleB)
// [{
// path: 'info.phone',
// code: 'type',
// message: 'Not a number'
// }]
validate 函数和 is 函数的用法类似,区别是 validate 函数并不是返回一个布尔值 ,而是一个包含错误信息的数组。
type
一种。DeepKit 中 serialize/deserialize 两个方法,为用户提供了序列化/反序列化的能力
import { serialize } from '@deepkit/type';
class MyModel {
id: number = 0;
created: Date = new Date;
constructor(public name: string) {
}
}
const model = new MyModel('Peter');
const jsonObject = serialize<MyModel>(model);
//{
// id: 0,
// created: 2022-11-02T17:49:03.240Z,
// name: 'Peter'
//}
serialize 方法接收类型信息和需要序列化的数据,将数据序列化为符合类型定义的JSON对象。
const myModel = deserialize<MyModel>({
id: 5,
created: 'Sat Oct 13 2018 14:17:35 GMT+0200',
name: 'Peter',
});
is<Date>(myModel.created)// true
deserialize 方法接收类型信息和需要反序列化的数据,将数据反序列化为符合类型信息定义的数据。代码中的 created
字段会被反序列化为 Date 字段。
一句话概括装饰器:装饰器本质上就是一个函数,可以在运行时对被装饰对象进行自定义的加工处理。
DeepKit 中提供了一套类型装饰器,这里的类型装饰器和 TypeScript 的装饰器并不相同,TypeScript 多用于对类的装饰,类型装饰器顾名思义是对类型的装饰。这些类型装饰器可以被当作一个正常的 TypeScript 类型使用。
举一个简单的例子
import { integer } from '@deepkit/type';
// case 1
type count = integer;
is<count>(1) // true
is<count>(1.1) // false
我们对定义 count 类型为 integer(整型),可以看到,1.1这个浮点数类型并没有通过校验。
除此之外,DeepKit 还实现了如 PrimaryKey(主键),maxLength/minLength(最小/最大长度)等功能的类型装饰器。我们可以把这些类型装饰器看作对于 TypeScript 类型的拓展,这些类型装饰器使 TypeScript 能够实现数据库级别的类型定义。也正是基于这套拓展后的运行时类型,验证和序列化可以有更多的约束,DeepKit 也实现了一套高性能的 ORM 。
@deepKit/type 给我们提供了一套运行时调用类型信息的方案。除此之外,DeepKit 的作者还基于类型信息和反射机制实现了更多的能力。
为了尽量压缩运行时的额外开销,DeepKit 的作者做出了不少优化。
在未使用泛型的情况下,DeepKit 会对使用到的类型对象进行缓存
// case1
type MyType = string;
typeOf<MyType>() === typeOf<MyType>(); //true
// case2
type MyType<T> = T;
typeOf<MyType<string>>() === typeOf<MyType<string>>();//false
可以看到,对于 case1 ,Mytype 对应的类型对象会被缓存,因此两次typeOf<MyType>()
的结果相等;但是对于泛型来说,我们无法确定传入的 T
具体是什么类型(理论上会有无限种),因此不会结果进行缓存,每次都会创建一个新的类型对象。
[]
image.png
DeepKit 的核心原理是一个类型编译器,它会介入TypeScript 的编译流程,保留类型信息, 在这个过程中,Deepkit 的类型编译器会读取源码中的类型信息,产生相关的字节码(为了使它尽可能小),并将其插入 AST 中,将其转化为另一个包含这些字节码信息的 TypeScript AST。
在运行时,DeepKit 会有一个迷你虚拟机,负责解析和执行这些字节码,最后会返回一个类型对象。
更详细的原理可以参考:https://github.com/microsoft/TypeScript/issues/47658
在 DeepKit 官方提供的性能图中,可以看到 DeepKit 在数据读写上的表现是比较优秀的,这也归功于 DeepKit 提供的 运行时类型信息,这种预先知晓类型信息的机制可以使 序列化/验证等更加快速高效。
DeepKit 是市场上第一个在 JavaScript 运行时提供全套 TypeScript 类型的解决方案。它使前端/服务端可以共用一套TypeScript定义的数据模型,并且使用基于 TypeScript 实现的一套反射机制。
但它依旧存在一些不足,比如 不支持外部类型,若代码中使用的类型信息来自第三方,且第三方库也没有经过 deepkit 的类型编译器的话,外部类型的类型信息在运行时也会全部丢失。
官方文档站:https://deepkit.io/
在TypeScript的仓库中,其实已经有许多人提出了issue,对在运行时保留Typescript的类型信息提出了自己的设想。可以看出,在基于 TypeScript支持动态类型这件事情上,是有需求的,但是 TypeScript 始终是保持保留意见,并没有实质去支持相关能力。
个人的看法,根本上是和 TypeScript 的设计目标[1] 挂钩, TypeScript 官方团队并不希望 TypeScript 会对运行时造成额外的开销,并且希望生成的 JavaScript 尽量纯净。TypeScript 官方团队 的保守严谨造就了 TypeScript 的成功。可能正因如此,TypeScript 官方团队才一直对支持运行时类型持保守态度。
https://deepkit.io/ https://github.com/microsoft/TypeScript/issues/47658
6月5日,一张券商降薪截图在社交媒体疯传。截图提到,当日上午,某中字头头部券商召开大会,除了MD外全员降薪,且降薪不只是降奖金,而是直接降底薪。按照职级不同,SA1降6K,SA3降8K,VP降8K—10K。据了解,降薪大概率整体属实,但具体幅度有所差异,且不同区域、不同业务条线目前掌握的降薪情况也不尽相同。
今日,蔚来 CEO 李斌在 2023 高通汽车技术与合作峰会上爆料,蔚来第二代技术平台的全系车型已标配第三代骁龙座舱平台。
Meta公司周一(5月22日)推出了一个开源AI语言模型——大规模多语言语音(Massively Multilingual Speech, MMS)模型,可以识别和产生1000多种语言的语音——比目前可用的模型增加了10倍。研究人员表示,他们的模型可以转换1000多种语言,但能识别4000多种语言。
歌手孙燕姿在更新动态中回应了近日引发争议的“顶流AI歌手孙燕姿”,笑称粉丝已经接受她是“冷门”歌手,而AI成为了目前的顶流。
5月31日晚,荣耀方面对澎湃新闻记者表示,上海荣耀智能科技开发有限公司是荣耀位于上海的研究所,是荣耀在中国的5个研究中心之一,重点方向在终端侧核心软件、图形算法、通信、拍照等方面研究开发工作。荣耀强调,坚持以用户为中心,开放创新,与全球合作伙伴一起为用户提供最佳产品解决方案。
据北京市市场监督管理局公示信息,5月24日,苹果电子产品商贸(北京)有限公司因发布虚假广告被北京市东城区市场监督管理局处以20万元的行政处罚。
据外媒5月24日消息,全球最大的个人电脑制造商联想表示,在2023年1-3月期间,该公司裁员了约5%,这是由于PC市场不景气导致的。
日前,有网络博主号称拍摄到了小米首款汽车MS11的高清视频。从视频中可以看出,新车依旧包裹大面积的伪装,据该博主称,他之所以确定这是小米汽车,是因为靠近观察之后,发现它的三角形大灯轮廓和其最初手绘的小米汽车假想图几乎一模一样。
超过 350 名从事人工智能工作的高管、研究人员和工程师签署了这份由非盈利组织人工智能安全中心发布的公开信,认为人工智能具备可能导致人类灭绝的风险,应当将其视为与流行病和核战争同等的社会风险。
日前,以押注“颠覆性创新”著称的ARK Invest创始人Cathie Wood在接受媒体采访时表示,软件提供商将是人工智能狂潮的下一个受益板块。英伟达每卖出1美元的硬件,软件供应商SaaS供应商就会产生8美元的收入。
据报道,阿里巴巴研究员吴翰清已于近期离职,钉钉显示其离职时间是5月19日。在阿里内部,研究员的职级为P10。据消息人士透露,吴翰清离职后,选择AI短视频赛道创业,已经close一轮融资。对于上述消息,截至发稿,阿里尚未回应。
阿里巴巴集团官微宣布,2023年六大业务集团总计需新招15000人,其中校招超过3000人。同时表示,“近日,关于淘宝天猫、阿里云、菜鸟、本地生活各个业务裁员谣言传得很厉害,但谣言就是谣言。我们的招聘正在紧锣密鼓的进行。”
“现今每一个存在的应用都将被AI 2.0重构,我觉得整个AI大模型带来的机遇和技术浪潮,会比过去Windows和安卓大10倍。”李开复表示。
苹果发布Vision Pro头显,正式宣布开启空间计算时代;苹果还发布新款MacBook Air,新款Mac Studio,并展示了iOS17、iPadOS 17、macOS Sonoma和watchOS10等新系统;Vision Pro头显售价3499美元,将于2024年初正式在美国市场发售;华尔街并不看好Vision Pro,苹果股价周一创历史新高后由涨转跌。
5月25日,长城汽车就比亚迪秦PLUS DM-i、宋PLUS DM-i采用常压油箱,涉嫌整车蒸发污染物排放不达标的问题进行举报。
近日,一个名为“贾跃亭”的抖音账号悄然出现,带有“FF创始人、合伙人、首席产品及用户生态官, LeEco 乐视创始人”等标签,IP 地址显示为美国。
5月29日消息,继上周远超预期的财报业绩预测引得股价和市值史诗级暴涨后,今日,英伟达(NVIDIA)创始人兼CEO黄仁勋穿着标志性的皮衣,意气风发地出现在台北电脑展COMPUTEX 2023上,在主题演讲期间先是现场给自家显卡带货,然后一连公布涉及加速计算和人工智能(AI)的多项进展。
近日,苹果位于天猫的Apple Store官方旗舰店挂出直播预告,表示将在5月31日晚19时开启官方直播,这也是苹果官方在电商平台的全球首次直播。
前京东集团副总裁、京东探索研究院副院长梅涛自今年初离职后,确认在 AI 领域创业,成立生成式 AI 公司 HiDream.ai。