【AICon】探索RAG 技术在实际应用中遇到的挑战及应对策略!AICon精华内容已上线73%>>> 了解详情
写点什么

重磅!TypeScript 3.7 RC 发布,备受瞩目的 Optional Chaining 来了

  • 2019-10-29
  • 本文字数:8378 字

    阅读完需:约 27 分钟

重磅!TypeScript 3.7 RC发布,备受瞩目的Optional Chaining来了

近日,微软发布了 TypeScript 3.7 RC,这是 TypeScript 3.7 的候选发布版本。到最终版本发布之前,除了重要的错误修复,微软表示,预计不会再有其他更改。下面我们来逐一看看有哪些新功能值得关注。


TypeScript 3.7 RC 提供了一些呼声最高的功能特性!


  • 可选链

  • 空值合并

  • 断言函数

  • 更好地支持返回 never 的函数

  • (更多)递归类型别名

  • –declaration 和 --allowJs

  • 使用项目引用进行免构建编辑

  • 未调用函数的检查

  • TypeScript 文件中的 // @ts-nocheck

  • 分号格式化选项

  • 重大更改

  • DOM 更改

  • 函数真实性检查

  • 本地和导入的类型声明现在会冲突

  • API 更改


你可以通过 NuGet 或以下 npm 命令开始使用 RC 版:


npm install typescript@rc
复制代码


你可以通过下列途径获得编辑器支持:



下面,我们来逐一介绍 TypeScript 3.7 的新功能。首先是最受瞩目的功能:可选链(Optional Chaining)

可选链

TypeScript 3.7 实现了呼声最高的 ECMAScript 功能之一:可选链(Optional Chaining)!我们的团队一直在深度参与 TC39 的标准制定,努力将这一功能推向第三阶段,从而将其带给所有的 TypeScript 用户。


那么什么是可选链呢?从本质上讲,有了可选链后,我们编写代码时如果遇到 null 或 undefined 就可以立即停止某些表达式的运行。可选链的核心是新的?. 运算符,用于 可选的属性访问。比如下面的代码:


let x = foo?.bar.baz();
复制代码


也就是说,当定义了 foo 时,将计算 foo.bar. baz();但如果 foo 为 null 或 undefined,程序就会停止运行并只返回 undefined。说得更清楚些,以上代码等效下面这种写法。


let x = (foo === null || foo === undefined) ?    undefined :    foo.bar.baz();
复制代码


请注意,如果 bar 为 null 或 undefined,我们的代码在访问 baz 时仍会出错。同样,如果 baz 为 null 或 undefined,我们在调用函数时也会出现错误。?. 只会检查其左侧的值是否为 null 或 undefined,而不检查任何后续属性。


你可能会使用?. 替换许多使用 && 运算符执行中间属性检查的代码。


// 之前if (foo && foo.bar && foo.bar.baz) {    // ...}
// 之后if (foo?.bar?.baz) { // ...}
复制代码


请记住?. 与 && 操作的行为有所不同,其中 && 操作特别针对的是“虚假”值(例如空字符串、0、NaN 和 false)。


可选链还包括其他两个操作。首先是 可选元素访问,其作用类似可选属性访问,但允许我们访问非标识符属性(例如任意字符串、数字和符号):


/** * 当我们有一个数组时,返回它的第一个元素 * 否则返回 undefined。 */function tryGetFirstElement<T>(arr?: T[]) {    return arr?.[0];    // 等效:    // return (arr === null || arr === undefined) ?    // undefined :    // arr[0];}
复制代码


另一个是 可选调用,如果表达式不是 null 或 undefined,我们可以有条件地调用它们。


async function makeRequest(url: string, log?: (msg: string) => void) {    log?.(`Request started at ${new Date().toISOString()}`);    // 等效:    // if (log !== null && log !== undefined) {    // log(`Request started at ${new Date().toISOString()}`);    // }
const result = (await fetch(url)).json();
log?.(`Request finished at at ${new Date().toISOString()}`);
return result;}
复制代码


可选链的“短路(short-circuiting)”行为仅限于“普通”和可选属性访问、调用和元素访问——它不会从这些表达式进一步扩展。换句话说,下面的代码不会阻止除法或 someComputation() 调用。


let result = foo?.bar / someComputation()
复制代码


它相当于:


let temp = (foo === null || foo === undefined) ?    undefined :    foo.bar;
let result = temp / someComputation();
复制代码


这可能会导致除法结果 undefined,这就是为什么在 strictNullChecks 中,以下代码会报错:


function barPercentage(foo?: { bar: number }) {    return foo?.bar / 100;    // ~~~~~~~~    // 错误:对象可能未定义。}
复制代码


有关更多详细信息可以查阅提案和原始的拉取请求

空值合并

另一个即将推出的 ECMAScript 功能是 空值合并运算符(nullish coalescing operator),它与可选链都是我们的团队一直努力推进的功能。


你可以设想一下这个功能——?? 运算符可以在处理 null 或 undefined 时“回退”到一个默认值上。例如下面的代码:


let x = foo ?? bar();
复制代码


这是一种新的途径来表示值 foo“存在”时会被使用;但当它为 null 或 undefined 时,就在其位置计算 bar()。


同样,以上代码等效于下面的写法。


let x = (foo !== null && foo !== undefined) ?    foo :    bar();
复制代码


尝试使用默认值时,?? 运算符可以代替||。例如,以下代码段尝试获取上次保存在 local-Storage 中的 volume 值(如果曾经保存过),但因为它使用了||,所以会报错。


function initializeAudio() {    let volume = localStorage.volume || 0.5
// ...}
复制代码


当 localStorage.volume 设置为 0 时,页面会意外地将 volume 设置为 0.5。?? 能避免将 0、NaN 和""中的某些意外行为视为虚假值。


我们非常感谢社区成员 Wenlu Wang 和 Titian Cernicova Dragomir 实现此功能!有关更多详细信息,请查看其拉取请求空值合并提案存储库

断言函数

如果发生意外情况,有一组特定的函数会 throw 一个错误。它们被称为“断言”函数。例如,Node.js 为此有一个专用函数,称为 assert。


assert(someValue === 42);
复制代码


在此示例中,如果 someValue 不等于 42,则 assert 将抛出 AssertionError。


JavaScript 中的断言通常用于防止传入不正确的类型。例如:


function multiply(x, y) {    assert(typeof x === "number");    assert(typeof y === "number");
return x * y;}
复制代码


不幸的是,在 TypeScript 中,这些检查永远无法正确编码。这意味着 TypeScript 对松散类型的代码检查更少,并经常在稍保守的代码类型中迫使用户使用类型断言。


function yell(str) {    assert(typeof str === "string");
return str.toUppercase(); // 哇!我们拼错了 'toUpperCase'。 // 希望 TypeScript 还是能捕获它!}
复制代码


替代方法是重写代码以便 TS 语言分析,但这并不方便。


function yell(str) {    if (typeof str !== "string") {        throw new TypeError("str should have been a string.")    }    // 错误捕获!    return str.toUppercase();}
复制代码


TypeScript 的终极目标是以破坏最少的方式嵌入现有的 JavaScript 结构中,所以 TypeScript 3.7 引入了一个称为“断言签名”的新概念,可以对这些断言函数建模。


第一种断言签名的建模方式和 Node 的 assert 函数的机制相同。它确保在作用域的其余部分中,无论检查的是什么条件都必须为真。


function assert(condition: any, msg?: string): asserts condition {    if (!condition) {        throw new AssertionError(msg)    }}
复制代码


asserts condition 表示,如果 assert 返回,则传递给 condition 参数的任何内容都必须为真(否则会抛出错误)。这意味着对于作用域的其余部分,该条件必须是真的。举个例子,使用这个断言函数意味着我们 确实 捕获了之前 yell 示例中的异常。


function yell(str) {    assert(typeof str === "string");
return str.toUppercase(); // ~~~~~~~~~~~ // 错误:属性 'toUppercase' 在 'string' 类型上不存在。 // 你说的是 'toUpperCase' 吗?}
function assert(condition: any, msg?: string): asserts condition { if (!condition) { throw new AssertionError(msg) }}
复制代码


另一种断言签名不检查条件,而是告诉 TypeScript 特定的变量或属性具有不同的类型。


function assertIsString(val: any): asserts val is string {    if (typeof val !== "string") {        throw new AssertionError("Not a string!");    }}
复制代码


这里的 assert val is string 确保在对 assertIs-String 进行任何调用之后,传入的任何变量都将是一个 string。


function yell(str: any) {    assertIsString(str);
// 现在 TypeScript 知道 'str' 是一个 'string'.
return str.toUppercase(); // ~~~~~~~~~~~ // 错误:属性 'toUppercase' 在 'string' 类型上不存在。 // 你说的是 'toUpperCase' 吗?
}
复制代码


这些断言签名与编写类型谓词签名非常相似:


function isString(val: any): val is string {    return typeof val === "string";}
function yell(str: any) { if (isString(str)) { return str.toUppercase(); } throw "Oops!";}
复制代码


就像类型谓词签名一样,这些断言签名也有着惊人的表现力。我们可以用它们表达一些相当复杂的想法。


function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {    if (val === undefined || val === null) {        throw new AssertionError(            `Expected 'val' to be defined, but received ${val}`        );    }}
复制代码


有关断言签名的更多信息,请查看原始的拉取请求

更好地支持返回 never 的函数

作为断言签名项目的一部分,TypeScript 需要对调用的函数和调用的位置编码更多信息。于是我们就可以扩展对另一类函数的支持:返回 never 的函数。


任何返回 never 的函数的意图都是说它不会返回。它表明抛出了异常,出现了暂停错误条件或程序已退出。例如,@types/node 中的 process.exit(…) 被指定为返回 never。


为了确保函数永远不会返回 undefined,或者说可以从所有代码路径中有效地返回,TypeScript 需要一些语法信号——在函数结尾 return 或 throw。这样用户就会发现自己正在 return 故障函数。


function dispatch(x: string | number): SomeType {    if (typeof x === "string") {        return doThingWithString(x);    }    else if (typeof x === "number") {        return doThingWithNumber(x);    }    return process.exit(1);}
复制代码


现在,当调用这些返回 never 的函数时,TypeScript 会识别出它们会影响控制流图,并说明原因。


function dispatch(x: string | number): SomeType {    if (typeof x === "string") {        return doThingWithString(x);    }    else if (typeof x === "number") {        return doThingWithNumber(x);    }    process.exit(1);}
复制代码


你可以在断言函数的拉取请求中查阅更多信息。

(更多)递归类型别名

“递归”引用类型别名时一直存在一些限制。原因是一旦用到类型别名就必须能用其别名来代替自己。在某些情况下这是不可能的,因此编译器会拒绝某些递归别名,如下所示:


type Foo = Foo;
复制代码


这是一个合理的限制,因为任何对 Foo 的使用都需要用 Foo 代替,而 Foo 则需要用 Foo 代替,而 Foo 则需要用 Foo 代替……好吧,希望你懂我的意思!最后没有哪一种类型可以代替 Foo。


这与其他语言对待类型别名的方式一致,但用户使用这一功能时会出现一些意想不到的问题。例如,在 TypeScript 3.6 和更低版本中,以下代码会报错。


type ValueOrArray<T> = T | Array<ValueOrArray<T>>;// ~~~~~~~~~~~~// 错误:类型别名 'ValueOrArray' 循环引用它自己。
复制代码


这很奇怪,因为从技术上讲这种用法并没有错,用户应该总是可以通过引入接口来编写实际上是相同的代码。


type ValueOrArray<T> = T | ArrayOfValueOrArray<T>;
interface ArrayOfValueOrArray<T> extends Array<ValueOrArray<T>> {}
复制代码


由于接口(和其他对象类型)引入了一定程度的间接性,并且不需要急切地构建其完整结构,因此 TypeScript 在使用该结构时没有问题。


但对于用户而言,引入界面的解决方法并不直观。原则上来说,ValueOrArray 的原始版本,也就是直接使用 Array 确实没有任何问题。如果编译器有点“懒惰”,并且仅在必要时才计算 Array 的类型参数,则 TypeScript 就可以正确表达这些参数。


这正是 TypeScript 3.7 引入的内容。在类型别名的“顶层”,TypeScript 将推迟解析类型参数以允许使用这些模式。


这意味着像下面这样的代码(试图表示 JSON)……


type Json =    | string    | number    | boolean    | null    | JsonObject    | JsonArray;
interface JsonObject { [property: string]: Json;}
interface JsonArray extends Array<Json> {}
复制代码


终于无需辅助接口也能重写了。


type Json =    | string    | number    | boolean    | null    | { [property: string]: Json }    | Json[];
复制代码


这种新的宽松条件也使我们可以在元组中递归引用类型别名。下面的代码以前会报错,现在则是有效的 TypeScript 代码:


type VirtualNode =    | string    | [string, { [key: string]: any }, ...VirtualNode[]];
const myNode: VirtualNode = ["div", { id: "parent" }, ["div", { id: "first-child" }, "I'm the first child"], ["div", { id: "second-child" }, "I'm the second child"] ];
复制代码


有关更多信息可以查阅原始的拉取请求

–declaration 和 --allowJs

TypeScript 中的 --declaration 标志允许我们从源 TypeScript 文件(如.ts 和.tsx 文件)生成.d.ts 文件(声明文件)。这些.d.ts 文件很重要,因为它们允许 TypeScript 对其他项目进行类型检查,而无需重新检查 / 构建原始源代码。出于相同的原因,使用项目引用时 需要 此设置。


不幸的是,–declaration 不适用于 --allowJs 之类的设置,无法混合使用 TypeScript 和 JavaScript 输入文件。这是一个令人沮丧的限制,因为它意味着用户即使在迁移代码库时也无法使用 --declaration,即使使用 JSDoc 注释也是如此。TypeScript 3.7 对此做了更改,并允许将这两种功能混合使用!


使用 allowJs 时,TypeScript 将尽可能理解 JavaScript 源代码,并将其以等效表示形式保存到.d.ts 文件中。这包括其中所有的 JSDoc 注释,所以像如下代码:


/** * @callback Job * @returns {void} */
/** Queues work */export class Worker { constructor(maxDepth = 10) { this.started = false; this.depthLimit = maxDepth; /** * 注意:队列中的作业可能会将更多项目添加到队列中 * @type {Job[]} */ this.queue = []; } /** * 在队列中添加一个工作项 * @param {Job} work */ push(work) { if (this.queue.length + 1 > this.depthLimit) throw new Error("Queue full!"); this.queue.push(work); } /** * 如果队列尚未开始就启动它 */ start() { if (this.started) return false; this.started = true; while (this.queue.length) { /** @type {Job} */(this.queue.shift())(); } return true; }}
复制代码


现在将转换为以下无需实现的.d.ts 文件:


/** * @callback Job * @returns {void} *//** Queues work */export class Worker {    constructor(maxDepth?: number);    started: boolean;    depthLimit: number;    /**     * 注意:队列中的作业可能会将更多项目添加到队列中     * @type {Job[]}     */    queue: Job[];    /**     * 在队列中添加一个工作项     * @param {Job} work     */    push(work: Job): void;    /**     * 如果队列尚未开始就启动它     */    start(): boolean;}export type Job = () => void;
复制代码


有关更多详细信息请查阅原始拉取请求

使用项目引用进行免构建编辑

TypeScript 的项目引用为我们提供了一种简便的方法来分解代码库,从而提升编译速度。不幸的是,编辑一个尚未建立依赖关系(或输出已过时)的项目时会出现很多问题。


在 TypeScript 3.7 中,当打开具有依赖项的项目时,TypeScript 将自动使用源.ts / .tsx 文件代替。这意味着使用项目引用的项目现在将获得增强的编辑体验,其中语义操作都是最新且“有效”的。你可以使用编译器选项 disableSourceOfProjectReferenceRedirect 禁用此行为,处理非常大的项目(此更改可能影响编辑性能)时用这个选项可能会很合适。


有关此更改的更多信息请查阅其拉取请求

未调用函数的检查

一个常见且危险的错误是忘记调用某个函数,尤其是当函数具有零参数,或者命名更像是属性而非函数时更容易出错。


interface User {    isAdministrator(): boolean;    notify(): void;    doNotDisturb?(): boolean;}
// 稍后……
// 代码已破坏, 勿用!function doAdminThing(user: User) { // oops! if (user.isAdministrator) { sudo(); editTheConfiguration(); } else { throw new AccessDeniedError("User is not an admin"); }}
复制代码


比如在上面的代码中我们忘记调用 isAdmini-strator,该代码错误地允许非管理员用户编辑配置!


在 TypeScript 3.7 中,这被标识为可能的错误:


function doAdminThing(user: User) {    if (user.isAdministrator) {    // ~~~~~~~~~~~~~~~~~~~~    // 错误!这个条件将始终返回 true,因为这个函数定义是一直存在的    // 你的意思是调用它吗?
复制代码


此检查是一项重大更改,但正因如此,检查会非常保守。仅在 if 条件下才发出此错误,并且在 strictNullChecks 关闭,或者以后在 if 的正文中调用该函数时,不会在可选属性上发出此错误:


interface User {    isAdministrator(): boolean;    notify(): void;    doNotDisturb?(): boolean;}
function issueNotification(user: User) { if (user.doNotDisturb) { // OK, 属性是可选的 } if (user.notify) { // OK, 调用了这一函数 user.notify(); }}
复制代码


如果你打算在不调用函数的情况下测试它,则可以将其定义更正为包含 undefined/null,或使用!! 编写类似 if(!!user.isAdministrator) 的内容以指示强制是故意的。


我们非常感谢 GitHub 用户 @jwbay,他创建了概念验证,并一直改进到现在这个版本。

TypeScript 文件中的 // @ts-nocheck

TypeScript 3.7 允许我们在 TypeScript 文件的顶部添加 // @ts-nocheck 注释以禁用语义检查。过去只有在存在 checkJs 时,JavaScript 源文件中的这一注释才会被认可。但我们已扩展了对 TypeScript 文件的支持,以简化所有用户的迁移过程。


分号格式化选项 TypeScript 的内置格式化程序现在支持在结尾分号可选的位置,根据 JavaScript 的自动分号插入(ASI)规则插入和删除分号。该设置现在在 Visual Studio Code Insiders 中可用,也在 Visual Studio 16.4 Preview 2 中的“Tools Options”菜单中可用。



选择“insert”或“remove”的值还会影响自动导入的格式、提取的类型以及 TypeScript 服务提供的其他生成代码。将设置保留为默认值“ignore”会使生成的代码与当前文件中检测到的分号首选项相匹配。

重大更改

DOM 更改

lib.dom.d.ts 中的类型已更新。这些更改大都是与可空性相关的正确性更改,但其影响最终取决于你的代码库。


https://github.com/microsoft/TypeScript/pull/33627

函数真实性检查

如上所述,当在 if 语句条件下似乎未调用函数时,TypeScript 现在会出错。除非满足以下任一条件,否则在 if 条件下检查函数类型时会发出错误:


  • 检查值来自可选属性。

  • strictNullChecks 已禁用。

  • 该函数稍后在 if 的正文中调用。

本地和导入的类型声明现在会起冲突

由于一个错误,之前的 TypeScript 版本允许以下构造:


// ./someOtherModule.tsinterface SomeType {    y: string;}
// ./myModule.tsimport { SomeType } from "./someOtherModule";export interface SomeType { x: number;}
function fn(arg: SomeType) { console.log(arg.x); // Error! 'x' doesn't exist on 'SomeType'}
复制代码


在这里,SomeType 似乎同时源于 import 声明和本地 interface 声明。可能想不到的是,在模块内部 SomeType 仅引用 import 的定义,而本地声明 SomeType 仅在从另一个文件导入时才可用。这非常令人困惑,我们对这类罕见情况的调查表明,开发人员通常会认为出现了一些其他问题。


TypeScript 3.7 现在可以正确地将其识别为重复的标识符错误。正确的修复方案取决于作者的初衷,具体情况具体分析。通常来说,命名冲突是无意的,最好的解决方法是重命名导入的类型。如果初衷是要扩展导入的类型,则应编写适当的模块扩展。

API 更改

为了支持前文所述的递归类型别名模式,我们已从 TypeReference 接口中删除了 typeArguments 属性。用户应该在 TypeChecker 实例上使用 getTypeArguments 函数。

未来计划

TypeScript 3.7 的最终版本将在几周内发布,理想情况下只需极少的更改即可完成!我们希望大家尝试这个 RC 版本并向我们提供反馈,从而打造一个出色的 3.7 版本。如果你有任何建议或遇到任何问题,请随时在我们的问题跟踪器上报告问题!


https://github.com/microsoft/TypeScript/issues/new/choose


编程快乐!


原文链接:


https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-rc/


2019-10-29 08:005082
用户头像
王文婧 InfoQ编辑

发布了 126 篇内容, 共 70.3 次阅读, 收获喜欢 274 次。

关注

评论 3 条评论

发布
用户头像
太香了.博主写的很棒.为了感谢博主推荐博主使用一款国产免费接口管理工具-ApiPost.辛苦辛苦
2021-08-26 16:54
回复
用户头像
好些个是源自 C# 的语法糖特性啦
2020-02-10 21:33
回复
用户头像
跟 Dart 越来越像了,Null 调用 ?.、Null 判断 ??、断言 assert ...
2019-10-29 10:40
回复
没有更多了
发现更多内容

ChatGPT的原理与前端领域实践 | 京东云技术团队

京东科技开发者

人工智能 前端 ChatGPT 企业号 5 月 PK 榜

惊艳!腾讯强推599页Netty进阶神技,完美诠释Netty

做梦都在改BUG

Java Netty

开发神技!阿里消息中间件进阶手册限时开源,请接住我的下巴

做梦都在改BUG

Java kafka 分布式 MQ 消息中间件

新一代企业数字化联盟成立,“强强联手”搭建品牌服务生态

数划云

数字化 全面预算管理 新一代企业数字化联盟 数划云 绩效分析

深度学习基础入门篇[9.3]:卷积算子:空洞卷积、分组卷积、可分离卷积、可变性卷积等详细讲解以及应用场景和应用实例剖析

汀丶人工智能

人工智能 深度学习 卷积网络 空洞卷积 分组卷积

SpringBoot 中异步任务实现及自定义线程池执行异步任务

做梦都在改BUG

Java Spring Boot

Topaz Gigapixel AI for Mac激活(图片无损放大软件) v6.3.2

真大的脸盆

Mac Mac 软件 图片无损放大 图片放大工具

低代码开发平台魔笔 X 浙江广电集团:“10天”成为行业最小创新单位!

移动研发平台EMAS

阿里云 低代码开发 魔笔

Serverless冷扩机器在压测中被击穿问题 | 京东云技术团队

京东科技开发者

Serverless GC 击穿 企业号 5 月 PK 榜 Serverless扩容

【等保】等保全称是什么?英文咋说?

行云管家

等保 等级保护 等保2.0

并发编程-常见并发工具BlockingQueue的使用及原理解析

做梦都在改BUG

Java 并发编程 BlockingQueue

真香!阿里最新产出分布式进阶实战手册,涵盖分布式架构所有操作

Java你猿哥

架构 分布式 微服务 Spring Cloud Spring Boot

Abaqus非线性问题预览及求解

思茂信息

仿真软件 abaqus abaqus软件 abaqus有限元仿真 有限元仿真技术

MSE 自治服务帮你快速定位解决 Dubbo 重复订阅导致 RPC 服务注册失败问题

阿里巴巴云原生

阿里云 云原生 dubbo MSE

PoseiSwap:为何青睐 Layer3?又为何选择 Celestia 作为技术伙伴?

鳄鱼视界

Java性能优化实践与策略

xfgg

Java 优化 规范

Openjob:更强大、更智能的分布式任务调度框架,重磅发布!

stelin

java; 后端、 分布式,

阿里微服务实施手册我粉了,原来微服务还可以这样玩

做梦都在改BUG

Java 架构 微服务 Spring Cloud

kafka生产者你不得不知的那些事儿

JAVA旭阳

Java kafka

大数据如何助力营销(5)活动复盘

MobTech袤博科技

软件测试/测试开发丨学习笔记之Pytest使用

测试人

Python 软件测试 自动化测试 测试开发 pytest

肝到头秃!百度强推并发编程笔记我爱了,原来这才叫并发

做梦都在改BUG

Java 并发编程

云纳管是什么意思?云纳管平台哪个好?

行云管家

云计算 云服务 云平台 云管平台 云纳管

京东购物车如何提升30%性能 | 京东云技术团队

京东科技开发者

性能优化 RPC 企业号 5 月 PK 榜 京东购物车 异步改造

Github星标百万!终于有人将Spring技术精髓收录成册

做梦都在改BUG

Java spring 框架

Deferred Components-实现Flutter运行时动态下发Dart代码 | 京东云技术团队

京东科技开发者

flutter dart 企业号 5 月 PK 榜 Deferred Components

探索未来智能交通:网联汽车与汽车互联

EMQ映云科技

车联网 物联网 智能交通 汽车互联

Kafka生产者你不得不知的那些事儿

做梦都在改BUG

深度学习基础入门篇[10]:序列模型-词表示{One-Hot编码、Word Embedding、Word2Vec、词向量的一些有趣应用}

汀丶人工智能

人工智能 深度学习 nlp 词向量 序列模型

IPQ8072 or IPQ8072A with the QCN9074/9024 chipset / well-suited for high-end routers.

Cindy-wallys

IPQ8072

OpenHarmony支持HDMI接口声卡适配说明

OpenHarmony开发者

OpenHarmony

重磅!TypeScript 3.7 RC发布,备受瞩目的Optional Chaining来了_大前端_Daniel Rosenwasser_InfoQ精选文章