函数是任何应用程序的基本构件
无论是本地函数
还是从模块中导入的函数
或者类上的函数等等
向其他很多值一样
这些函数也是一个值
TypeScript 有很多方法来描述如何调用函数
fn: (a: string) => void
// 参数是一个函数
function greeter(fn: (a: string) => void) {
fn("hello world!")
}
// 此函数符合 greeter 参数的标准
function printSth(s: string) {
console.log(s)
}
// 调用 greeter 传入 printSth 函数
greeter(printSth)
PS D:\MyFile\VSCodeProjects\study-ts\第 5 章 函数\dist> node .\01-function.js
hello world!
两种写法等效,这种写法似乎更优雅一点点!
// 定义一个类型别名
type greeterFuntion = (a: string) => void
// 参数是一个函数
function greeter(fn: greeterFuntion) {
fn("hello world!")
}
// 此函数符合 greeter 参数的标准
function printSth(s: string) {
console.log(s)
}
// 调用 greeter 传入 printSth 函数
greeter(printSth)
在 JavaScript 除了可调用之外
函数也可以有属性
然而函数类型表达式的语法不允许声明属性
如果我们想用属性来描述可调用的东西
可以在一个对象类型中写一个调用签名
2022.02.08 01:02:20 增补内容:
那么对象类型就像一个函数了!可以像调用函数一样调用对象!
比如:
let message: string = "hello world" message()
,其中的message()
是会报错的!因为 message 没有调用签名!参考下面的代码示例,DescribableFunction 里面有调用签名,调用签名就类似“函数部分”,有了它就可以像调用函数一样调用对象类型!可以使用诸如
message()
的形式进行调用,与调用函数一样!
2022.02.08 09:19:02 增补内容:
调用签名实现了什么?实现了使得一个对象像一个函数一样调用!即带上括号,甚至有参数和返回值!
type DescribableFunction = {
description: string, // 属性
// 下面这行代码叫做【调用签名】
(someArg: number): boolean // 函数类型表达式,注意这里使用的是冒号(:),而不是箭头(=>)!
}
type DescribableFunction = {
description: string, // 属性
(someArg: number): boolean // 函数类型表达式
}
function doSth(fn: DescribableFunction){
console.log(fn.description + "returned" + fn(6))
}
function fn1(num: number) {
console.log(num)
return true
}
// 必须给函数绑定属性,否则下一行会报错!
fn1.description = "hello world"
doSth(fn1)
PS D:\MyFile\VSCodeProjects\study-ts\第 5 章 函数\dist> node .\02-sign.js
6
hello worldreturnedtrue
JavaScript 的函数可以用 new 操作符来调用
TypeScript 将这些称之为构造函数
因为它们通常会创建一个新的对象
我们可以通过在调用签名之前加一个 new 关键字来写一个构造签名
(s: string): Ctor
这些内容叫做【调用签名!】
// 多一个 new
type SomeConstructor = {
new (s: string): Ctor
}
2022.02.08 09:38:09 增补内容:
这里构造签名的参数与类的构造函数对应,具体参考第 8 章:类!
class Ctor {
s: string
constructor(s: string) {
this.s = s
}
}
type SomeConstructor = {
new (s: string): Ctor
}
// SomeConstructor 可以将其理解为一个构造函数 或者是 类 反正都返回是要可以实例化的对象
function fn(ctor: SomeConstructor) {
return new ctor("难以理解的代码!")
}
const f = fn(Ctor)
console.log(f.s)
PS D:\MyFile\VSCodeProjects\study-ts\第 5 章 函数\dist> node .\03-constructor.js
难以理解的代码!
type CallOrConstructor = {
new (s: string): Date
(n?: number): number
}
function fn(date: CallOrConstructor) {
let a = new date("2022-2-7")
let b = date(100)
}
在写函数的时候输入的类型与输出的类型常常存在一定关系
我们会使用发泛型来解决
此处,函数输入与返回都是any,我们希望函数返回值类型是数组元素的类型,这就用到了泛型!
function firstElement(arr any[]) {
return arr[0]
}
泛型就是两个值之间存在的对应关系,我们就用泛型来去解决
字母 T 是随便写的!T 也就是 Type 的缩写!或者其他字母即可
function firstElement(arr: T[]): T | undefined {
return arr[0]
}
function firstElement(arr: any[]){
return arr[0];
}
// 我们希望函数返回值类型就是数组元素的类型,这就用到了泛型
function firstElement1(arr: T[]): T | undefined {
return arr[0]
}
//函数的类型是在函数调用时决定的
const l = firstElement1(['1','2','3']) // 1 l 是string类型
const r = firstElement1([1,2,3,4]) // 1 r是number类型
const s = firstElement1([]) // undefined s是undefined类型
function firstElement(arr: T[]): T | undefined {
return arr[0]
}
firstElement(["a", "b", "c"])
报错原因:因为 输入类型T 并不是固定的是 ,会随着T任何类型发生改变
function firstElement(arr: T[]): T | undefined {
return 100 // 报错:不能将类型“100”分配给类型“T | undefined”。ts(2322)
}
firstElement(["a", "b", "c"])
报错原因:因为 T 类型并不是固定的是 number 类型!
function firstElement(arr: T[]): T | undefined {
return 100 // 报错:不能将类型“100”分配给类型“T | undefined”。
}
firstElement([1, 2, 3])
这里的 可以不写,但是如果写了后面的数组元素类型就必须与其一致!
一般不写,TS 自动推断即可!
function firstElement(arr: T[]): T | undefined {
return arr[0]
}
firstElement([1, 2, 3]) // 报错:不能将类型“number”分配给类型“string”。
说明:
- 两个泛型参数 T、O
- 函数的第一个参数是 T 类型数组,第二个参数是返回 O类型的回调函数
- 函数的返回值是 O 数组
// 多个泛型
//函数的返回值是 Output数组
function firstElement2(arr:Input[] , fn:(s:Input)=>Output):Output[]{
// 调用的是数组的map方法,里面需要传入一个函数
return arr.map(fn).sort()
}
let resutl = firstElement2(['1','7','2','4'],(n)=>{let num = parseInt(n); return num})
console.log(resutl);
[Running] node "e:\TS\day03\list\04-generic-functions.js"
string 1 undefined
[ 1, 2, 4, 7 ]
有时候我们希望一个泛型是某个类型的子集
此时我们就需要对泛型的取值范围进行限制了
这和 Java 不一样
T extends { length: number }
这使得 T 一定具有属性 length(是限制条件而不是扩展条件),且是 number 类型!
function longest(a: T, b: T) {
if (a.length >= b.length) {
return a
} else {
return b
}
}
// 定义函数,要求类型 T 必须具有 length 属性,且是 number 类型
function longest(a: T, b: T) {
if (a.length >= b.length) {
return a
} else {
return b
}
}
// 调用测试
const a = longest([1, 2, 3], [4, 5])
const b = longest("hello", "hi")
// const c = longest(1000, 200) // 报错:类型“number”的参数不能赋给类型“{ length: number; }”的参数。
// const huenhe = langget([1,2,4],'hello') // 不可以 泛型变量只能被赋一种
使用通用约束条件时的常见错误!
分支返回是一个对象 这个对象貌似是已经是我们这个限制条件了 但代码承诺返回的类型一定是泛型类型
function minimumLength (obj: T, minimum: number): T {
if (obj.length > minimum) {
return obj
} else {
// 报错:不能将类型“{ length: number; }”分配给类型“T”。
// "{ length: number; }" 可赋给 "T" 类型的约束,但可以使用约束 "{ length: number; }" 的其他子类型实例化 "T"。
return { length: minimum }
}
}
function minimumLength (obj: T, minimum: number): T {
if (obj.length > minimum) {
return obj
} else {
// 报错:不能将类型“{ length: number; }”分配给类型“T”。
// "{ length: number; }" 可赋给 "T" 类型的约束,但可以使用约束 "{ length: number; }" 的其他子类型实例化 "T"。
return { length: minimum }
}
}
// 测试
const arr = minimumLength([1, 2, 3], 5) //TS帮我们自动推断出是数值数组 但是对象没有slice()这个方法
// 执行时报错:arr.slice is not a function
console.log(arr.slice(0))
这结尾不清不楚,{length:number}只是Type的约束条件,而obj.length>=minmum条件不成立时返回Type 返回一个对象可以使用类型断言 let val={length:minmum}as Type
function getneric(obj:T,minimum:number):T{
if(obj.length >= minimum){
return obj
}
return {length:minimum} as T
}
console.log(getneric([1,2,3,4],7));
// 执行结果为
[Running] node "e:\TS\day03\list\17-parameter-destrucuring.js"
{ length: 7 }
TypeScript 通常可以推断出通用的函数调用中预期的类型参数
但并非总是如此
此时我们需要在调用类型参数的时候指定类型参数
const arr = combine([1, 2, 3], ["hello"])
function combine(arr1: T[], arr2: T[]): T[] {
return arr1.concat(arr2)
}
const arr1 = combine([1, 2, 3], [4, 5, 6])
// 报错:不能将类型“string”分配给类型“number”。
// 因为此时 ts 将类型推断为数值类型
const arr2 = combine([1, 2, 3], ["hello"])
// 我们可以主动告诉数组的类型为联合类型 string | number
const arr3 = combine([1, 2, 3], ["hello"])
可能的情况下,使用参数本身,而不是对其进行约束
// 此写法更规范,推断返回类型为 T
function firstElement1(arr: T[]) {
return arr[0]
}
// 推断返回类型为 any
function firstElement2(arr: T) {
return arr[0]
}
// 调用
const x = firstElement1([1, 2, 3])
const y = firstElement2([1, 2, 3])
// 结论:可能的情况下,使用参数本身,而不是对其进行约束
总是尽可能少得使用类型参数
// 写法1
function filter1(arr: T[], func: (arg: T) => boolean) {
return arr.filter(func)
}
// 写法2
function filter2 boolean>(arr: T[], func: Func) {
return arr.filter(func)
}
// 评论:第一种写法更好,第二种写法的 Func 除了使得函数更复杂之外,什么也没做!
如果一个类型的参数只出现在一个地方,请重新考虑你是否真的需要它
// 完全没必要使用泛型
function greet(s: Str) {
console.log("hello " + s)
}
// 更简洁
function greet1(s: string) {
console.log("hello " + s)
}
JavaScript 中的一些函数经常需要一个可变数量的参数
function f(n: number) {
console.log(n.toFixed()) // 0个参数
console.log(n.toFixed(3)) // 1个参数
}
// 我们可以在参数后面加一个问号(?)表示该参数是可选的
function fn(n?: number) {} // 此时可传入参数 n ,也可以不传!
// 扩展:默认参数,如果想不传参数也可以设置默认参数,如下:
function fn(n: number = 100) {} // 不传参数的时候 n 的值为 100
当为回调写一个函数类型时
永远不要写一个可选参数
除非你打算在不传递该参数的情况下调用函数
因为在编写调用回调的函数时容易出错!
// 定义函数,这里的 index 是可选参数!
function myForEach(arr: any[], callback: (arg: any, index?: number) => void){
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i)
}
}
// 调用测试
myForEach([1, 2, 3], a => console.log(a))
myForEach([10, 20, 30], (a, i) => console.log(a, i))
// index 是可选参数,当不传入时
function myForEach1(arr: any[], callback: (arg: any, index?: number) => void){
for (let i = 0; i < arr.length; i++) {
callback(arr[i])
}
}
myForEach1([1, 2, 3], a => console.log(a))
myForEach1([10, 20, 30], (a, i) => console.log(a, i))
当回调函数的可选参数未传入时,为 undefined ,可能会导致很多错误!
PS D:\MyFile\VSCodeProjects\study-ts\第 5 章 函数\dist> node .\10-callbacks.js
1
2
3
10 0
20 1
30 2
1
2
3
10 undefined
20 undefined
30 undefined
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 {
// ...
}
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(2, 9, 2022)
// 报错:没有需要 2 参数的重载,但存在需要 1 或 3 参数的重载。
// 参数是由重载签名决定的,实现签名只是为了适配重载签名,且对外部是隐藏的!
// 所以这里没有能够传两个参数的重载!
const d3 = makeDate(2, 9)
function fn(x: string): void // 重载签名带参数
function fn(){} // 实现签名无参数
// 此处传入参数并不报错,也就再次说明实现签名是隐藏的!
fn("hello")
此处存疑!与老师讲的不同!
function fn(x: boolean): void
function fn(x: string): void
function fn(x: boolean | string) {
console.log(typeof x)
console.log(x)
}
// 测试
fn("hello")
fn(true)
此处存疑!与老师讲的不同!
返回什么类型的值都可以,我觉得是不合理的,应该手动添加多个重载签名返回值类型的联合类型!
// 老师的这里会报错!学生们的不会!此处是按照学生的写的!
function fn(x: boolean): string
function fn(x: string): boolean
function fn(x: boolean) {
if(Math.random() > 0.5){
return 1
} else {
return "111"
}
}
console.log(typeof fn("111")) // number (这不是固定的)
console.log(typeof fn(true)) // string (这不是固定的)
在可能的情况下
总是倾向于使用联合类型的参数
而不是重载函数
function len(x: string): number
function len(arr: any[]): number
function len(a: any) {
return a.length
}
len("hello")
len([1, 2, 3])
// 报错了,我们不能传入一个可能是"hello"也可能是数组的参数,这个类型是联合类型 "hello" | number[]
len(Math.random() > 0.5 ? "hello" : [100, 200 ,300])
// 解决方法:将函数重载换成联合类型
function len(x: string | any[]) {
return a.length
}
len("hello")
len([1, 2, 3])
len(Math.random() > 0.5 ? "hello" : [100, 200 ,300])
TypeScript 会通过代码流分析来推断函数中的 this 应该是什么
const user = {
id: 123,
admin: false,
becomeAdmin: function () {
// 此处的 this.admin 即指代的是上面的 admin
this.admin = true
}
}
// this 作为函数的参数
interface DB {
filterUsers(filter: (this: User) => boolean): User[]
}
此处的 this 完全体现不出来作用!换成其他的也会得到同样的结果!
interface User {
admin: boolean
}
// this 作为函数的参数
interface DB {
filterUsers(filter: (this: User) => boolean): User[]
}
const db: DB = {
filterUsers: (filter: (this: User) => boolean) => {
let user1: User = { admin: true }
let user2 : User = { admin: true }
let user3: User = { admin: false }
return [user1, user2, user3]
}
}
const admins = db.filterUsers(function(this: User) {
return this.admin
})
console.log(admins) // [ { admin: true }, { admin: true }, { admin: false } ]
我没有使用 this ,到底 this 怎么使用,还需要进一步探索!
interface Target{
adming:boolean,
age:number
}
interface DB2 {
filterTarget(filter:(target:Target)=>boolean):Target[]
}
const db2:DB2 ={
filterTarget(filer:(target:Target)=>boolean){
let target1: Target ={
adming:false,
age:25
}
let target2: Target ={
adming:true,
age:18
}
let target3: Target = {
adming:true,
age:39
}
let targetTotal = [target1,target2,target3]
let nullTartget = []
for(let i = 0; i
表示没有任何返回值的函数的返回值;
自动推断返回值为 void
function go () { return } // 有没有 return 无所谓
object 指的是任何不是基元的值:string、number、bigint、boolean、symbol、null、undefined
小写的object不同与 {} 、也不同于 Object
在 JavaScript 中函数值是对象
它们有属性
比如在原型链里有 Object.prototype
是 Object 的一个实例
可以调用 Object.key 等等
TypeScript 也有对应,也就是小写的 object
注意!小写的 object 不是 大写的 Object
在 TS 中始终使用小写的 object
unknown 类型代表任何值
这与 any 类型类似
但更安全
因为对未知 unknown 值做任何使其都是不合法的!
function fn1(a: any) {
a.x // 正常
}
function fn1(a: unknown) {
a.x // 报错:对象的类型为 "unknown"。
}
never 表示永远不会被观察到的值
返回值为 never 的情况:抛出异常;终止执行;死循环。那他的返回值就是never了
function fail(msg: string): never {
throw new Error(msg)
}
function fail(x: string | number) {
if (typeof x === "string") {
// do sth
} else if (typeof x === "number") {
// do sth
} else {
// 此时为 never 类型
}
}
全局性的 Function 类型描述了诸如 bind、call、apply 和其他存在于 JavaScript 中所有函数值的属性
他还有一个特殊的属性
即 Function 类型的值总是可以被调用
这些调用返回 any
返回类型是 any ,一般情况下我们避免这么做,因为 any 作为返回类型是不安全的!
function doSth(f: Function) {
return f(1, 2, 3)
}
如果我们需要接受一个任意的函数,而不打算调用它,可以使用箭头函数的方式定义类型
() => void
可选参数和函数重载可以让函数接收各种固定数量的参数
现在我们使用展开运算符来定义函数
可以接收无限数量的参数
此处的 m 相当于一个数组,所有参数自第二个起被放进这个数组里面!
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x)
}
const arr = multiply(10, 1, 2, 3, 4, 5)
上一节,我们可以把真实的参数合并带形参里
通过展开运算符放到一个变量里
相反,我们也可以将一个数组展开到一个调用函数里
const arr100 = [1, 2, 3]
const arr200 = [4, 5, 6]
arr100.push(...arr200)
const arr100 = [1, 2, 3]
const arr200 = [4, 5, 6]
arr100.push(...arr200) // 与下效果相同
arr100.push(7, 8, 9, 10) // 与上效果相同
console.log(arr100)
PS D:\MyFile\VSCodeProjects\study-ts\第 5 章 函数\dist> node .\17-spread.js
[
1, 2, 3, 4, 5,
6, 7, 8, 9, 10
]
const arr100 = [8, 9]
const x = Math.atan2(8, 9) // 没问题
const y = Math.atan2(...arr100) // 报错:扩张参数必须具有元组类型或传递给 rest 参数。
// 此处是因为 Math.atan2() 的参数只能是两个,而 arr100 的元素个数是不确定的
关于
as const
参考文章:blog.csdn.net/weixin_4326…
const arr100 = [8, 9] as const // 将 8 个 9 断言为常量(文字类型)
const x = Math.atan2(...arr100)
我们可以使用参数重构的方法
将对象实参解压到函数主体的一个或多个局部变量中
function sum({a, b, c}: {a: number, b: number, c: number}) { .// 参数是用一个对象解构的方式
console.log(a + b + c)
}
sum({ a: 10, b: 20, c: 30 })
函数的 void 返回类型可以产生一些不寻常的行为
这种行为我们看上去确实预期的
返回类型为 void 上下文类型
并不强迫函数不返回任何东西
一个具有 void 返回类型的上下文函数类型(type vf = () => void)
在实现时,可以返回任何其他值,但它会被忽略 问题:但是实际上可以打印出来返回值!
当一个字面的函数定义有一个 void 返回类型时
该函数必须不返回任何东西
此时出现的必须注意的问题,我的 f1() f2() f3() 打印出来是有结果的!并非被忽略了!
// 情况一
type voidFunc = () => void
// 函数实现写法一
const f1: voidFunc = () => {
return "hello"
}
// 函数实现写法二
const f2: voidFunc = () => true
// 函数实现写法三
const f3: voidFunc = function() {
return 100
}
// 调用:老师讲的返回值会被忽略,但在我这里却可以正常打印!
console.log(f1()) // hello
console.log(f2()) // true
console.log(f3()) // 100
// 情况二
function go(): void {
return "hello" // 报错:不能将类型“string”分配给类型“void”。
}
const f5 = function(): void {
return true
}// 报错的