函数
在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。
函数类型
为函数定义类型
我们可以在为函数的每个参数定义类型后,为函数的返回值指定类型。但TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。
// 匿名函数
let myAdd = function(x: number, y: number): number { return x + y; };
// 命名函数
function add(x: number, y: number): number {
return x + y;
}
完整的函数类型
第一种:参数类型和返回值类型。
参数类型列表中我们为每个参数指定一个参数名和一个参数类型,参数名可以随便起,只要类型正确就可以。
返回值在参数类型后定义,并且要在前面加一个 => 箭头增加可读性,就算这个函数没有返回值,也要指定返回值类型为 void,不能留空。
let myAdd: (x:number, y:number) => number =
function(x: number, y: number): number { return x + y; };
在赋值语句中,我们可以发现,如果在 = 号的一侧指定了类型但是没有在另一侧指定,TypeScript 会帮我们自动识别出类型。
// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return x + y; };
// The parameters `x` and `y` have the type number
let myAdd: (baseValue: number, increment: number) => number =
function(x, y) { return x + y; };
可选参数和默认参数
在TypeScript中,每个函数参数都是必须的,并且不可以传入多余参数。
可选参数
如果要实现可选参数,可以在参数旁边添加?
例如:
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
需要注意的是:可选参数一定要跟在比选参数的后面。
默认参数
我们也可以为参数提供一个默认值,当用户没有传递这个参数或传递的值是undefined时。它们叫做有默认初始化值的参数。 带默认值的参数都是可选参数,可选参数的类型与默认值的类型保持一致。
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
需要注意的是:默认参数可以不跟在比选参数的后面,但需要注意的是,如果默认参数在前面,用户必须明确的传入undefined值来获得默认值。
例如:
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result4 = buildName(undefined, "Adams");
剩余参数
有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。在javascript中,可以通过arguments来访问所有参数。 在TypeScript中,你可以把所有参数都放进一个变量。
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
🌟this指向和箭头函数
JavaScript里,this的值在函数被调用的时候才会指定。 例子:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
return function() {
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);
可以看到createCardPicker是个函数,并且它又返回了一个函数。
当我们运行这个程序的时候会报错,为什么呢?因为在这个代码片段中,执行createCardPicker时,this指向的不是 deck 而是 window,因为我们只是独立的调用了 createCardPicker。
顶级的非方法式调用会将 this 视为 window(注意:在严格模式下,this为undefined而不是window)。
怎么解决这个问题呢?我们可以在函数返回时绑定好正确的this值,这样的话,无论之后怎么使用它,都会引用绑定的‘deck’对象。
我们需要改变函数表达式来使用ECMAScript 6箭头语法。 箭头函数能保存函数创建时的this值,而不是调用时的值:
修改后:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// 注意: 下面的函数使用了箭头函数语法,可以捕获到这里的 'this'
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);
但是,如果编译选项中设置了noImplicitThis,TypeScript就会警告你出现了一个错误,因为noImplicitThis表示: 当this表达式的值为any类型的时候,生成一个错误。
在这个例子中,this的类型就是any。
如何解决这个问题呢?
this 参数
解决的的方法是,提供一个显式的this参数,this 参数是一个假的参数,它出现在参数列表的最前面。 重构上面的例子:
// 添加接口
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// 提示: 这个函数现在显式的指定它的调用方式 this 必须是Deck类型
createCardPicker: function(this: Deck) {
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);
现在typeScript知道createCardPicker期望在某个Deck对象上调用,this指向的Deck对象而不是Any,noImplicitThis就不会报错了。
回调函数中的this参数
当你把函数传递到某个库函数的回调函数内执行时,也会出现this为undefined的问题,这是因为当回调函数被调用时,它被当作普通函数,this为undefined。 如何解决这个问题? 首先,库函数的作者需要指定this的类型
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
被调用的函数
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// 注意,在这里使用this,调用这个回调时会崩溃
this.info = e.message;
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // 报错!
为什么会报错呢? 在接口指定了this类型后,你显式声明onClickBad必须在Handler的实例上调用。而Typescript又监测出addClickListener需要一个void类型的this,所以会报错。 改变一下this的指定类型再试一下:
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// 把this的类型改变成void后,就不能在这里使用this了,因为它的类型是void啦!
console.log('clicked!');
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
如果你又想在回调函数里面使用this,又不想报错,那就只能用箭头函数了
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = e.message }
}
这样是可行的,因为箭头函数并不会捕获this
重载
JavaScript是个动态语言,函数根据传入的不同参数返回不同类型的结果是很常见的事情。比如下面这个例子:
// 牌的类型数组:红桃,黑桃,梅花,方块
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x): any {
// 如果类型是 object\array, 从用户手中抽牌,传入你现在的牌的数组,返回随机抽到的你的牌
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// 如果是数字,则根据数字发对应的牌
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);
上面这个方法在类型系统中怎么表示呢? 答案是为同一个函数提供多种函数类型定义来进行重载!(没想到吧,呵呵呵)下面是代码
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 {
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
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);
这样,这个函数在调用的时候就会进行正确的类型检查。 注意: 在定义重载的时候,一定要把最精确的定义放在最前面。(为啥?你猜) 注意: function pickCard(x): any并不是重载列表的一部分,因此这里只有两个重载:一个是接收对象另一个接收数字。 以其它参数调用pickCard会产生错误。