TypeScript 05 - 函数
函数是一等公民,与数值、字符串和数组等地位相等,可以被命名、赋值,可以作为参数传递给另一个函数,也可以作为另一个函数的返回值。在 TS 中,函数依然是主要的定义“行为”的地方,只是 TS 添加了额外功能。
- 函数类型
- 可选参数和默认参数
- 剩余参数
- this
- 重载
1. 函数类型
函数类型包含两部分:参数类型和返回值类型。一个匿名函数表达式的完整函数类型如下:
let myAdd: (x: number, y: number) => number =
function(x: number, y: number): number { return x + y; };
写出完整的函数类型太麻烦了,其实可以省略等号左边或者右边的类型,TS 会自动地进行类型推断。
2. 可选参数和默认参数
在TypeScript里我们可以在参数名旁使用 ?
实现可选参数的功能,可选参数必须跟在必须参数后面:
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
而默认参数包含了可选参数,可传可不传,传了就要是默认值那种类型:
function buildName(firstName: string, lastName = "Smith") {
// ...
}
至于默认参数的位置,可以不像可选参数那样放在最后面,但如果默认参数放在前面,当调用函数时即使采用默认参数策略,为了保证参数列表的顺序对应,也要明确传递一个 undefined
占位:
function buildName(firstName = "Bruce", lastName: string) {
return firstName + " " + lastName;
}
let result = buildName(undefined, 'Li'); // Bruce Li
3. 剩余参数
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。
function buildName(firstName: string, ...restOfName: string[]) {
console.log(restOfName); // 一个剩余参数都没传递时,restOfName 是 []
return firstName + ' ' + restOfName.join(" ");
}
let studentName = buildName('Nicholas', 'Super', 'peng');
剩余参数的省略号也可以用在带有剩余参数的函数类型定义上:
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
4. this
this 和 JS 中的一样,判断 this 需要找到函数的调用位置,简单来说大概有四种:
new
绑定 > 显示绑定(call
、apply
、bind
) > 隐式绑定(上下文对象中调用) > 默认绑定(严格模式下绑定到 undefined
,非严格模式下绑定到全局对象,如 window
对象)。
ES6 箭头函数能保存函数创建时的 this 值,而不是调用时的值,即由当前所在的词法作用域决定。
下例中,为了解决 this 不会进行默认绑定到全局对象 ,所以使用了箭头函数:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
但不幸是,this
的类型是 any
,修复方法是添加两个接口,并提供显示的 this
参数。
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: string[];
createCardPicker(this: Deck): () => Card;
}
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function(this: Deck) {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
回调函数中的 this
有时候在回调函数依赖 this
是可能报错的,因为将回调函数作为参数传给某库函数被调用时,调用环境发生了变化。库函数作者可能不希望用户提供的回调函数中依赖 this
,所以可能会这样指定 this
类型:
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
作为用户,需要提供匹配的参数类型:
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use this here because it's of type void!
console.log('clicked!');
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
但如果既要满足接口约定,又想访问当前的 this
,则不得不使用箭头函数(箭头函数的参数压根不能有 this
参数,所以也不用写 this: void
了):
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = e.message }
}
5. 重载
TS 的重载和 Java 不同,TS 的设计原则之一就是不把类型检查带到runtime,所以编译好后的纯 JS 代码就不存在 TS 那一整套了。TS 的重载机制更主要是为了函数在调用的地方能进行正确的类型检查(在真正运行编译后的 JS 之前)。
在定义重载的时候,一定要把最精确的定义放在最前面。
写法如下,需要注意下面的 pickCard
函数只有两个重载(即只有两种调用传参方式),function pickCard(x): any
并不是重载列表的一部分,那是重载函数的实现:
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);