函数

函数是一切JavaScript应用中的最基本的代码块。在TypeScript中,有类和模块,但函数仍在“做实事”这方面扮演了关键的角色。TypeScript为传统的JavaScript函数增强了不少能力,使之更易用。

函数

JavaScript一样,TypeScript中也可以创建具名函数或匿名函数。选择创建哪种函数完全取决于你的应用。

让我们先看看,在JavaScript中,它们的样子:

  1. //Named function
  2. function add(x, y) {
  3. return x+y;
  4. }
  5. //Anonymous function
  6. var myAdd = function(x, y) { return x+y; };

和在JavaScript中一样,一个函数可以返回在自己作用域外的变量。虽然解释这种机制超出了本文的范围,但是理解这种机制,以及使用这种技术的局限,对于理解JavaScriptTypeScript函数是至关重要的。

  1. var z = 100;
  2. function addToZ(x, y) {
  3. return x+y+z;
  4. }

函数类型

为函数添加类型

让我们来为先前的例子添加上类型:

  1. function add(x: number, y: number): number {
  2. return x+y;
  3. }
  4. var myAdd = function(x: number, y: number): number { return x+y; };

我们可以为函数的每一个参数指定类型,包括函数的返回值。TypeScript会通过函数中的return声明来检测返回值的类型正确性。当然,你也可以不指定它。

书写函数类型

我们已经在一个函数上添加了类型,现在,让我们来为一个变量指定一个函数类型:

  1. var myAdd: (x:number, y:number)=>number =
  2. function(x: number, y: number): number { return x+y; };

函数类型包含两部分:参数类型和返回值类型。当书写函数类型时,这两部分都是必须的。当我们书写参数类型时,给定的参数名仅仅是为了增强可读性,正真实现函数时不必和它们的名字一致:

  1. var myAdd: (baseValue:number, increment:number)=>number =
  2. function(x: number, y: number): number { return x+y; };

第二部分是返回值类型。我们在参数类型和返回值类型之间使用一个胖箭头(=>)隔开了它们。正如之前提到的,这是必须的,如果函数没有返回值,你需要指定其为void

另外,函数类型只能指定参数和返回值类型。外部作用域的变量是不反应在这些类型上面的。实际上,这些都是一个函数的“隐式状态”,无法反应在它的API上。

类型推导

TypeScript中,只要你在等号一边指定了类型,编译器便会帮你去检查类型了:

  1. // myAdd has the full function type
  2. var myAdd = function(x: number, y: number): number { return x+y; };
  3. // The parameters 'x' and 'y' have the type number
  4. var myAdd: (baseValue:number, increment:number)=>number =
  5. function(x, y) { return x+y; };

这被称作“上下文类型”,是一种类型推导。它能帮助你减少代码量。

可选参数和默认参数

JavaScript中不同,在TypeScript中,所有的参数都是必选的。当函数被调用时,编译器会去检查每一个参数是否的确传递了。简而言之,传入的参数数量必须和定义时一样。

  1. function buildName(firstName: string, lastName: string) {
  2. return firstName + " " + lastName;
  3. }
  4. var result1 = buildName("Bob"); //error, too few parameters
  5. var result2 = buildName("Bob", "Adams", "Sr."); //error, too many parameters
  6. var result3 = buildName("Bob", "Adams"); //ah, just right

JavaScript中,每一个参数都是可选的,如果用户没有传递它,那它就是undefined。在TypeScript中,我们可以通过在参数后添加?来实现可选参数,例子:

  1. function buildName(firstName: string, lastName?: string) {
  2. if (lastName)
  3. return firstName + " " + lastName;
  4. else
  5. return firstName;
  6. }
  7. var result1 = buildName("Bob"); //works correctly now
  8. var result2 = buildName("Bob", "Adams", "Sr."); //error, too many parameters
  9. var result3 = buildName("Bob", "Adams"); //ah, just right

可选参数一定要放在必选参数的后面。如果我们想要将firstName变为可选,我们需要将其定义的顺序放于lastName之后。

TypeScript中,如果用户没有传递这个参数,我们可以为其提供一个默认参数:

  1. function buildName(firstName: string, lastName = "Smith") {
  2. return firstName + " " + lastName;
  3. }
  4. var result1 = buildName("Bob"); //works correctly now, also
  5. var result2 = buildName("Bob", "Adams", "Sr."); //error, too many parameters
  6. var result3 = buildName("Bob", "Adams"); //ah, just right

和可选参数一样,默认参数必须在必选参数之后。

默认参数和可选参数的类型是共享的:

  1. function buildName(firstName: string, lastName?: string) {
  2. function buildName(firstName: string, lastName = "Smith") {

它们都共享类型(firstName: string, lastName?: string)=>string。在类型里,默认参数的默认值不见了,变成了一个可选参数标识。

剩余参数

必选,可选,默认参数,都有一个共同点,它们都关注于一个参数。但有时,你想同时处理多个参数,或者你不能明确知道函数被调用时实际的参数数量。在JavaScript中,你可以在一个函数体内使用arguments变量得到它们。

TypeScript中,你可以把这些参数全放入一个变量中:

  1. function buildName(firstName: string, ...restOfName: string[]) {
  2. return firstName + " " + restOfName.join(" ");
  3. }
  4. var employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

剩余参数被视为是任意数量的可选参数。编译器会以数组的形式,将它们全部放入你在...后指定的参数中,使它们能让你在函数中使用。

类型中也可以使用剩余参数:

  1. function buildName(firstName: string, ...restOfName: string[]) {
  2. return firstName + " " + restOfName.join(" ");
  3. }
  4. var buildNameFun: (fname: string, ...rest: string[])=>string = buildName;

Lambda函数,以及使用this

JavaScript编程中,理解this是如何运作的是每一个程序员进阶的必经之路。TypeScript作为JavaScript的超集,TypeScript开发者也需要学习this的运作机制,并且正确地使用它。这里,我们对其做一个基本的介绍。

JavaScript中,this是一个当函数被调用时会被设置的值。它非常的强大和灵活,通过它可以知道函数当前的执行上下文。但它往往不是很清晰了当,比如当函数作为回调函数被执行时。

例子:

  1. var deck = {
  2. suits: ["hearts", "spades", "clubs", "diamonds"],
  3. cards: Array(52),
  4. createCardPicker: function() {
  5. return function() {
  6. var pickedCard = Math.floor(Math.random() * 52);
  7. var pickedSuit = Math.floor(pickedCard / 13);
  8. return {suit: this.suits[pickedSuit], card: pickedCard % 13};
  9. }
  10. }
  11. }
  12. var cardPicker = deck.createCardPicker();
  13. var pickedCard = cardPicker();
  14. alert("card: " + pickedCard.card + " of " + pickedCard.suit);

当我们试图运行以上代码时,我们将会得到一个异常。这是因为,在cardPicker()被调用时,createCardPicker函数所创建出的对象中的this会被设置为window,而不是deck对象。注意,在严格模式下,this将会是undefined,而不是window

我们需要在调用返回的函数之前,将this绑定到正确的值上。这样,不论之后怎么调用它,this都只会指向deck对象。

为了达到这个目的,我们将使用lambda语法(()=>{})来书写函数声明。这样会自动地将this绑定为当前的下文环境:

  1. var deck = {
  2. suits: ["hearts", "spades", "clubs", "diamonds"],
  3. cards: Array(52),
  4. createCardPicker: function() {
  5. // Notice: the line below is now a lambda, allowing us to capture 'this' earlier
  6. return () => {
  7. var pickedCard = Math.floor(Math.random() * 52);
  8. var pickedSuit = Math.floor(pickedCard / 13);
  9. return {suit: this.suits[pickedSuit], card: pickedCard % 13};
  10. }
  11. }
  12. }
  13. var cardPicker = deck.createCardPicker();
  14. var pickedCard = cardPicker();
  15. alert("card: " + pickedCard.card + " of " + pickedCard.suit);

更多关于this的详情,你可以参阅 这里

重载

JavaScript天生就是一个非常动态的语言。单个的JavaScript函数,可能会根据传入参数的类型不同,而返回不同类型的返回值。

  1. var suits = ["hearts", "spades", "clubs", "diamonds"];
  2. function pickCard(x): any {
  3. // Check to see if we're working with an object/array
  4. // if so, they gave us the deck and we'll pick the card
  5. if (typeof x == "object") {
  6. var pickedCard = Math.floor(Math.random() * x.length);
  7. return pickedCard;
  8. }
  9. // Otherwise just let them pick the card
  10. else if (typeof x == "number") {
  11. var pickedSuit = Math.floor(x / 13);
  12. return { suit: suits[pickedSuit], card: x % 13 };
  13. }
  14. }
  15. var myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
  16. var pickedCard1 = myDeck[pickCard(myDeck)];
  17. alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
  18. var pickedCard2 = pickCard(15);
  19. alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

这里的pickCard函数将会根据参数类型,返回两种类型的结果。这在TypeScript的类型系统中,我们该如何描述它们呢?

答案是通过重载,即支持多个不同参数类型的同名函数。当编译器解析函数调用时,它自动得会去这个函数列表中选择合适的函数。例子:

  1. var suits = ["hearts", "spades", "clubs", "diamonds"];
  2. function pickCard(x: {suit: string; card: number; }[]): number;
  3. function pickCard(x: number): {suit: string; card: number; };
  4. function pickCard(x): any {
  5. // Check to see if we're working with an object/array
  6. // if so, they gave us the deck and we'll pick the card
  7. if (typeof x == "object") {
  8. var pickedCard = Math.floor(Math.random() * x.length);
  9. return pickedCard;
  10. }
  11. // Otherwise just let them pick the card
  12. else if (typeof x == "number") {
  13. var pickedSuit = Math.floor(x / 13);
  14. return { suit: suits[pickedSuit], card: x % 13 };
  15. }
  16. }
  17. var myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
  18. var pickedCard1 = myDeck[pickCard(myDeck)];
  19. alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
  20. var pickedCard2 = pickCard(15);
  21. alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

有了重载,在我们调用pickCard时,我们也会得到类型检查。

为了使编译器能够正常地执行类型检测,它有一个非常简单的运行机制,编译器会查找此函数的重载列表,然后尝试以当前参数类型执行第一个函数,如果参数类型匹配,那么就视它为正确的重载。所以,通常来说,重载列表中的函数类型越往后越宽泛比较合适。

注意,function pickCard(x): any并不在函数的重载列表中,所以该函数仅有两个重载:一个接受对象参数,另一个接受数字参数。使用其他类型的参数调用pickCard都会得到一个报错。