TypeScript - 函数(中)

 

目录

1、编写良好泛型函数的准则

1.1 向下推送类型参数

1.2 使用较少的类型参数

1.3 类型参数应出现两次

2、可选参数

3、回调中的可选参数

4、函数重载

5、重载签名和实现签名

6、写好重载


1、编写良好泛型函数的准则

编写泛型函数很有趣,并且很容易被类型参数冲昏头脑。 类型参数过多或在不需要的地方使用约束会使推理不太成功,从而使函数的调用方感到沮丧。

1.1 向下推送类型参数

以下是编写看起来相似的函数的两种方法:

function catArr<Type>(params: Type[]) {return params;
}function dogArr<Type extends any[]>(params: Type){return params;
}catArr(['波斯猫', '橘猫', '拿破仑'])
dogArr(['哈士奇', '金毛', '博美'])

乍一看,这些可能看起来完全相同,但catArr是编写此函数的更好方法。其推断的返回类型为type,但dogArr的推断返回类型为any,因为TypeScript必须使用约束类型解析arr[0]表达式,而不是在调用期间“等待”解析元素。

注意:如果可能,请使用类型参数本身,而不是约束它。

1.2 使用较少的类型参数

参考上面的代码,我们改写一下:

function catArr<Type>(params: Type[], func: (arg: Type) => Type): Type[] {return params.filter(func);
}function dogArr<Type, Func extends (arg: Type) => Type>(params: Type[], func: Func): Type[] {return params.filter(func);
}

我们已经创建了一个类型参数Func,它与两个值不相关。这总是一个危险信号,因为这意味着想要指定类型参数的调用方必须毫无理由地手动指定一个额外的类型参数。Func什么也没做,只是让这个函数更难阅读和推理!

注意:始终使用尽可能少的类型参数

1.3 类型参数应出现两次

有时我们忘记了一个函数可能不需要是泛型的:

function cat<Name extends string>(N: Name): string {console.log('猫的名字是:: ' + N );return N;
}
cat('拿破仑')
// 猫的名字是:: 拿破仑

我们可以很容易地编写一个更简单的版本:

function cat(N: string): string {console.log('猫的名字是:: ' + N );return N;
}
cat('拿破仑')
// 猫的名字是:: 拿破仑

请记住,类型参数用于关联多个值的类型。如果一个类型参数在函数签名中只使用过一次,那么它与任何内容都没有关联。这包括推断的返回类型;例如,如果Name是cat的推断返回类型的一部分,那么它将关联参数和返回类型,因此尽管在编写的代码中只出现一次,但仍将使用两次。

注意:如果类型参数只出现在一个位置,请重新考虑是否确实需要它

2、可选参数

JavaScript 中的函数通常采用可变数量的参数。例如数字转2,8,16进制。

function N(n: number) {console.log(n.toString());    // 17console.log(n.toString(2));   // 0001console.log(n.toString(8));   // 21console.log(n.toString(16));  // 11
}
N(17)

我们可以在 TypeScript 中对此方法进行改造,将参数标记为可选?

function N(x:number, n?: number) {console.log(x.toString(n));  // 17
}
N(17)

还可以提供参数默认值,例如不传进制参数默认为十六进制:

function N(x:number, n = 16) {console.log(x.toString(n));  // 11
}
N(17)

现在在一下内容中f函数,n 是一个数字类型,因为任何undefined参数都会被替换成10,注意,当参数是可选的时候,调用方总是可以传递未定义的参数,因为这只是模拟一个“错误”的参数:

 

function N(x : number, n? : number) {console.log(x.toString(n));  // 11
}
N(17)            // 17
N(17, 10)        // 17
N(17, undefined) // 17

3、回调中的可选参数

了解可选参数和函数类型表达式后,在编写调用回调的函数时很容易犯以下错误:

function catArr(arr: any[], callback: (arg: any, index?: number) => void){let count = arr.length;for(let i =0 ; i< count; i++){callback(arr[i], i)}
}catArr(['波斯猫', '橘猫', '拿破仑'], (v) =>{console.log(v);
})
catArr(['波斯猫', '橘猫', '拿破仑'], (v, i) =>{console.log(v, i);
})

如果在回调函数的可选参数,调用原型方法时,可以会发生错误。

catArr(['波斯猫', '橘猫', '拿破仑'], (v, i) =>{console.log(v, i.toString());  // “i”可能为“未定义”。
})

在 JavaScript 中,如果调用的函数的参数多于参数,则忽略额外的参数。 TypeScript 的行为方式相同。 具有较少参数(相同类型)的函数始终可以取代具有更多参数的函数。

注意:为回调编写函数类型时,切勿编写可选参数,除非您打算在不传递该参数的情况下调用对应的方法。

4、函数重载

一些JavaScript函数可以在各种参数计数和类型中调用。例如,您可以编写一个函数来生成日期,该日期采用时间戳(一个参数)或月/日/年规范(三个参数)。

在TypeScript中,我们可以指定一个函数,该函数可以通过编写重载签名以不同的方式调用。为此,请编写一定数量的函数签名(通常为两个或多个),然后是函数的主体:

function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {if (d !== undefined && y !== undefined) {return new Date(y, mOrTimestamp, d);} else {return new Date(mOrTimestamp);}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);     // 没有需要 2 参数的重载,但存在需要 1 或 3 参数的重载。

在这个例子中,我们写了两个重载:一个接受一个自变量,另一个接受三个自变量。前两个签名称为过载签名。

然后,我们编写了一个具有兼容签名的函数实现。函数有一个实现签名,但不能直接调用此签名。尽管我们在所需参数之后编写了一个带有两个可选参数的函数,但它不能用两个参数调用!

5、重载签名和实现签名

这是造成混淆的常见来源。 通常人们会写这样的代码,而不明白为什么会有错误:

 

function fn(x: string): void;
function fn() {// ...
}
fn();   // 应有 1 个参数,但获得 0 个。

同样,用于编写函数体的签名无法从外部“看到”。

从外部看不到实现的签名。在编写重载函数时,应该始终在函数的实现之上有两个或多个签名。

实现签名还必须与重载签名兼容。 例如,这些函数存在错误,因为实现签名与重载不匹配:

function fn(x: boolean): void;
function fn(x: string): void;  // 此重载签名与其实现签名不兼容。
function fn(x: boolean) {}

function fn(x: string): string;
function fn(x: number): boolean;   // 此重载签名与其实现签名不兼容。
function fn(x: string | number) {return "oops";
}

6、写好重载

与泛型一样,在使用函数重载时应遵循一些准则。 遵循这些原则将使函数更易于调用、更易于理解和更易于实现。

让我们考虑一个返回字符串或数组长度的函数:

 

function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {return x.length;
}

这个功能很好;我们可以用字符串或数组调用它。 但是,我们不能使用可能是字符串数组的值来调用它,因为 TypeScript 只能将函数调用解析为单个重载:

len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);// 没有与此调用匹配的重载。
//   第 1 个重载(共 2 个),“(s: string): number”,出现以下错误。
//     类型“number[] | "hello"”的参数不能赋给类型“string”的参数。
//       不能将类型“number[]”分配给类型“string”。
//   第 2 个重载(共 2 个),“(arr: any[]): number”,出现以下错误。
//     类型“number[] | "hello"”的参数不能赋给类型“any[]”的参数。
//       不能将类型“string”分配给类型“any[]”。

由于两个重载具有相同的参数计数和相同的返回类型,因此我们可以改为编写函数的非重载版本:

function len(x: any[] | string) {return x.length;
}

这要好得多! 调用方可以使用任何一种值来调用它,作为额外的好处,我们不必找出正确的实现签名。

注意:尽可能首选具有联合类型的参数,而不是重载。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/5412.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

递归函数:

含义&#xff1a;自己调自己 递归三要素&#xff1a;定义函数、终止条件和等价关系式 小案例&#xff1a;排序 let arr1 [8, 8, 9, 13, 45, 8, 0, 1, 9, 66];//定义函数function quickSort(arr) {//终止条件if (arr.length < 1) return arr;const baseIndex Math.floor(…

uniapp顶部导航栏被遮住显示问题

解决uniapp顶部导航栏被遮住显示问题 uniapp官方给了处理的方案&#xff0c;即css变量&#xff0c;–status-bar-height&#xff0c;小程序这个值是25px&#xff0c;app则根据实际情况去变化 如下&#xff1a; //头部导航栏 <view class"header"> </view…

软件破解专题01

玩脱壳&#xff1f; 破解的功能&#xff1a;可以把收费软件免费使用 推荐论坛&#xff1a;pyg论坛》www.chinapyg.com 逆向&#xff1a;团队合作&#xff0c;有条件可以参加ctf大赛&#xff0c;这个应该是进步最快的途径 一个很强的开源项目&#xff1a;de4dot 作者…

Redis(主从复制、哨兵模式、集群)概述及部署

Redis&#xff08;主从复制、哨兵模式、集群&#xff09;概述及部署 一、Redis主从复制1、Redis主从复制的概念2、Redis主从复制的作用3、Redis主从复制的流程4、Redis主从复制的搭建 二、Redis 哨兵模式1、哨兵模式的原理2、哨兵模式的作用3、哨兵模式的结构4、哨兵模式的搭建…

springboot校园点餐小程序

校园点餐系统 springboot校园点餐系统小程序 java校园点餐小程序 技术&#xff1a; 基于springbootvue小程序校园点餐系统的设计与实现 运行环境&#xff1a; JAVA版本&#xff1a;JDK1.8 IDE类型&#xff1a;IDEA、Eclipse都可运行 数据库类型&#xff1a;MySql&#xff08;…

SpringSecurity(三):自定义认证数据源(源码+落地实现)。

自定义认证数据源 前言认证流程分析易混梳理AuthenticationManager 与 ProviderManagerProviderManager 与 AuthenticationProvider难点&#xff1a;为什么ProviderManager会有一个parent&#xff1f; 数据源的获取配置AuthenticationManager默认的全局 AuthenticationManager自…

SpringBoot源码分析(1)--@SpringBootApplication注解使用和原理/SpringBoot的自动配置原理详解

文章目录 前言主启动类的配置1、SpringBootApplication注解1.1、SpringBootConfiguration注解验证启动类是否被注入到spring容器中 1.2、ComponentScan 注解ComponentScan 注解解析与路径扫描 1.3、EnableAutoConfiguration注解1.3.1、AutoConfigurationPackage注解1.3.2、Impo…

YOLO系列正负样本分配策略

1、YOLOv3 使用MaxIoUAssigner策略来给gt分配样本&#xff0c;基本上保证每个gt都有唯一的anchor对应&#xff0c;匹配的原则是该anchor与gt的IOU最大且大于FG_THRESH&#xff0c;这种分配制度会导致正样本比较少&#xff0c;cls和bbox分支训练起来可能比较慢。在剩余的anchor…

【微服务架构模式】微服务设计模式

这是微服务架构系列文章的第 3 篇 高可用性、可扩展性、故障恢复能力和性能是微服务的特征。您可以使用微服务架构模式来构建微服务应用程序&#xff0c;从而降低微服务失败的风险。 模式分为三层&#xff1a; 应用模式 应用程序模式解决了开发人员面临的问题&#xff0c;例如数…

vue表格实现一个简单的合并单元格功能

用的是vue2ant-design-vue 但是vue3或者element-ui也是同理 先上效果 需要后端的数据将相同id的放在一起 否则也会有问题 例如&#xff1a; this.list [{id: 1,name: 舟山接收站,...}{id: 2,name: 舟山接收站碳中和LNG,...},{id: 2,name: 舟山接收站碳中和LNG,...} ]// th…

Java-数据结构(一)-java1中有哪些数据结构呢?

这里写目录标题 前言一、为什么需要数据结构&#xff1f;1、低效的操作2、占用过多的内存空间3、困难的数据操作 二、枚举&#xff08;Enumeration&#xff09;1、定义2、关键字3、适用场景 三、 位集合&#xff08;BitSet&#xff09;1、定义2、方法3、适用场景 四、向量&…

联邦学习 (FL) 中常见的3种模型聚合方法

联邦学习 (FL) 中常见的3种模型聚合方法 联合学习 (FL) 是一种出色的 ML 方法&#xff0c;它使多个设备&#xff08;例如物联网 (IoT) 设备&#xff09;或计算机能够在模型训练完成时进行协作&#xff0c;而无需共享它们的数据。 “客户端”是 FL 中使用的计算机和设备&#x…