TypeScript 详解之 TypeScript 的函数类型

简介

函数的类型声明,需要在声明函数时,给出参数的类型和返回值的类型。

function hello(
  txt:string
):void {
  console.log('hello ' + txt);
}

上面示例中,函数hello()在声明时,需要给出参数txt的类型(string),以及返回值的类型(void),后者写在参数列表的圆括号后面。void类型表示没有返回值,详见后文。

如果不指定参数类型(比如上例不写txt的类型),TypeScript 就会推断参数类型,如果缺乏足够信息,就会推断该参数的类型为any

返回值的类型通常可以不写,因为 TypeScript 自己会推断出来。

function hello(txt:string) {
  console.log('hello ' + txt);
}

上面示例中,由于没有return语句,TypeScript 会推断出函数hello()没有返回值。

不过,有时候出于文档目的,或者为了防止不小心改掉返回值,还是会写返回值的类型。

如果变量被赋值为一个函数,变量的类型有两种写法。

// 写法一
const hello = function (txt:string) {
  console.log('hello ' + txt);
}

// 写法二
const hello:
  (txt:string) => void
= function (txt) {
  console.log('hello ' + txt);
};

上面示例中,变量hello被赋值为一个函数,它的类型有两种写法。写法一是通过等号右边的函数类型,推断出变量hello的类型;写法二则是使用箭头函数的形式,为变量hello指定类型,参数的类型写在箭头左侧,返回值的类型写在箭头右侧。

写法二有两个地方需要注意。

首先,函数的参数要放在圆括号里面,不放会报错。

其次,类型里面的参数名(本例是txt)是必须的。有的语言的函数类型可以不写参数名(比如 C 语言),但是 TypeScript 不行。如果写成(string) => void,TypeScript 会理解成函数有一个名叫 string 的参数,并且这个string参数的类型是any

type MyFunc = (string, number) => number;
// (string: any, number: any) => number

上面示例中,函数类型没写参数名,导致 TypeScript 认为参数类型都是any

函数类型里面的参数名与实际参数名,可以不一致。

let f:(x:number) => number;

f = function (y:number) {
  return y;
};

上面示例中,函数类型里面的参数名为x,实际的函数定义里面,参数名为y,两者并不相同。

如果函数的类型定义很冗长,或者多个函数使用同一种类型,写法二用起来就很麻烦。因此,往往用type命令为函数类型定义一个别名,便于指定给其他变量。

type MyFunc = (txt:string) => void;

const hello:MyFunc = function (txt) {
  console.log('hello ' + txt);
};

上面示例中,type命令为函数类型定义了一个别名MyFunc,后面使用就很方便,变量可以指定为这个类型。

函数的实际参数个数,可以少于类型指定的参数个数,但是不能多于,即 TypeScript 允许省略参数。

let myFunc:
  (a:number, b:number) => number;

myFunc = (a:number) => a; // 正确

myFunc = (
  a:number, b:number, c:number
) => a + b + c; // 报错

上面示例中,变量myFunc的类型只能接受两个参数,如果被赋值为只有一个参数的函数,并不报错。但是,被赋值为有三个参数的函数,就会报错。

这是因为 JavaScript 函数在声明时往往有多余的参数,实际使用时可以只传入一部分参数。比如,数组的forEach()方法的参数是一个函数,该函数默认有三个参数(item, index, array) => void,实际上往往只使用第一个参数(item) => void。因此,TypeScript 允许函数传入的参数不足。

let x = (a:number) => 0;
let y = (b:number, s:string) => 0;

y = x; // 正确
x = y; // 报错

上面示例中,函数x只有一个参数,函数y有两个参数,x可以赋值给y,反过来就不行。

如果一个变量要套用另一个函数类型,有一个小技巧,就是使用typeof运算符。

function add(
  x:number,
  y:number
) {
  return x + y;
}

const myAdd:typeof add = function (x, y) {
  return x + y;
}

上面示例中,函数myAdd()的类型与函数add()是一样的,那么就可以定义成typeof add。因为函数名add本身不是类型,而是一个值,所以要用typeof运算符返回它的类型。

这是一个很有用的技巧,任何需要类型的地方,都可以使用typeof运算符从一个值获取类型。

函数类型还可以采用对象的写法。

let add:{
  (x:number, y:number):number
};

add = function (x, y) {
  return x + y;
};

上面示例中,变量add的类型就写成了一个对象。

函数类型的对象写法如下。

{
  (参数列表): 返回值
}

注意,这种写法的函数参数与返回值之间,间隔符是冒号:,而不是正常写法的箭头=>,因为这里采用的是对象类型的写法,对象的属性名与属性值之间使用的是冒号。

这种写法平时很少用,但是非常合适用在一个场合:函数本身存在属性。

function f(x:number) {
  console.log(x);
}

f.version = '1.0';

上面示例中,函数f()本身还有一个属性foo。这时,f完全就是一个对象,类型就要使用对象的写法。

let foo: {
  (x:number): void;
  version: string
} = f;

函数类型也可以使用 Interface 来声明,这种写法就是对象写法的翻版,详见《Interface》一章。

interface myfn {
  (a:number, b:number): number;
}

var add:myfn = (a, b) => a + b;

上面示例中,interface 命令定义了接口myfn,这个接口的类型就是一个用对象表示的函数。

Function 类型

TypeScript 提供 Function 类型表示函数,任何函数都属于这个类型。

function doSomething(f:Function) {
  return f(1, 2, 3);
}

上面示例中,参数f的类型就是Function,代表这是一个函数。

Function 类型的值都可以直接执行。

Function 类型的函数可以接受任意数量的参数,每个参数的类型都是any,返回值的类型也是any,代表没有任何约束,所以不建议使用这个类型,给出函数详细的类型声明会更好。

箭头函数

箭头函数是普通函数的一种简化写法,它的类型写法与普通函数类似。

const repeat = (
  str:string,
  times:number
):string => str.repeat(times);

上面示例中,变量repeat被赋值为一个箭头函数,类型声明写在箭头函数的定义里面。其中,参数的类型写在参数名后面,返回值类型写在参数列表的圆括号后面。

注意,类型写在箭头函数的定义里面,与使用箭头函数表示函数类型,写法有所不同。

function greet(
  fn:(a:string) => void
):void {
  fn('world');
}

上面示例中,函数greet()的参数fn是一个函数,类型就用箭头函数表示。这时,fn的返回值类型要写在箭头右侧,而不是写在参数列表的圆括号后面。

下面再看一个例子。

type Person = { name: string };

const people = ['alice', 'bob', 'jan'].map(
  (name):Person => ({name})
);

上面示例中,Person是一个类型别名,代表一个对象,该对象有属性name。变量people是数组的map()方法的返回值。

map()方法的参数是一个箭头函数(name):Person => ({name}),该箭头函数的参数name的类型省略了,因为可以从map()的类型定义推断出来,箭头函数的返回值类型为Person。相应地,变量people的类型是Person[]

至于箭头后面的({name}),表示返回一个对象,该对象有一个属性name,它的属性值为变量name的值。这里的圆括号是必须的,否则(name):Person => {name}的大括号表示函数体,即函数体内有一行语句name,同时由于没有return语句,这个函数不会返回任何值。

注意,下面两种写法都是不对的。

// 错误
(name:Person) => ({name})

// 错误
name:Person => ({name})

上面的两种写法在本例中都是错的。第一种写法表示,箭头函数的参数name的类型是Person,同时没写函数返回值的类型,让 TypeScript 自己去推断。第二种写法中,函数参数缺少圆括号。

可选参数

如果函数的某个参数可以省略,则在参数名后面加问号表示。

function f(x?:number) {
  // ...
}

f(); // OK
f(10); // OK

上面示例中,虽然参数x后面有问号,表示该参数可以省略。

参数名带有问号,表示该参数的类型实际上是原始类型|undefined,它有可能为undefined。比如,上例的x虽然类型声明为number,但是实际上是number|undefined

function f(x?:number) {
  return x;
}

f(undefined) // 正确

上面示例中,参数x是可选的,等同于说x可以赋值为undefined

但是,反过来就不成立,类型显式设为undefined的参数,就不能省略。

function f(x:number|undefined) {
  return x;
}

f() // 报错

上面示例中,参数x的类型是number|undefined,表示要么传入一个数值,要么传入undefined,如果省略这个参数,就会报错。

函数的可选参数只能在参数列表的尾部,跟在必选参数的后面。

let myFunc:
  (a?:number, b:number) => number; // 报错

上面示例中,可选参数在必选参数前面,就报错了。

如果前部参数有可能为空,这时只能显式注明该参数类型可能为undefined

let myFunc:
  (
    a:number|undefined,
    b:number
  ) => number;

上面示例中,参数a有可能为空,就只能显式注明类型包括undefined,传参时也要显式传入undefined

函数体内部用到可选参数时,需要判断该参数是否为undefined

let myFunc:
  (a:number, b?:number) => number; 

myFunc = function (x, y) {
  if (y === undefined) {
    return x;
  }
  return x + y;
}

上面示例中,由于函数的第二个参数为可选参数,所以函数体内部需要判断一下,该参数是否为空。

参数默认值

TypeScript 函数的参数默认值写法,与 JavaScript 一致。

设置了默认值的参数,就是可选的。如果不传入该参数,它就会等于默认值。

function createPoint(
  x:number = 0,
  y:number = 0
):[number, number] {
  return [x, y];
}

createPoint() // [0, 0]

上面示例中,参数xy的默认值都是0,调用createPoint()时,这两个参数都是可以省略的。这里其实可以省略xy的类型声明,因为可以从默认值推断出来。

function createPoint(
  x = 0, y = 0
) {
  return [x, y];
}

可选参数与默认值不能同时使用。

// 报错
function f(x?: number = 0) {
  // ...
}

上面示例中,x是可选参数,还设置了默认值,结果就报错了。

设有默认值的参数,如果传入undefined,也会触发默认值。

function f(x = 456) {
  return x;
}

f2(undefined) // 456

具有默认值的参数如果不位于参数列表的末尾,调用时不能省略,如果要触发默认值,必须显式传入undefined

function add(
  x:number = 0,
  y:number
) {
  return x + y;
}

add(1) // 报错
add(undefined, 1) // 正确

参数解构

函数参数如果存在变量解构,类型写法如下。

还有 56% 的精彩内容
©著作权归作者所有,转载或内容合作请联系作者
支付 ¥8.99 继续阅读
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容