TS学习第四节(函数)

793 阅读6分钟

学习网站

函数是JavaScript应用程序的基础。它帮助我们实现抽象类,模拟类,信息隐藏和模块。在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要定义行为的地方。TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易的使用。

函数

和JavaScript一样,TypeScript函数可以创建有名字函数和匿名函数。可以随意选择适合应用程序的方式,不论是定义一系列API函数还是只使用一次函数。

函数类型

为函数定义类型

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

我们可以给每个参数添加类型之后再为函数本身添加返回值类型。TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。

书写完整的函数类型

let myAdd: (x: number, y: number) => number = 
    function(x: number, y: number): number { return x + y };

函数类型包含两部分:参数类型和返回值类型。当写出完整函数类型的时候,这两部分都是需要的。我们以参数列表的形式写出参数类型,为每个参数指定一个名字和类型,这个名字只是为了增加可读性。 我们也可以这么写:

let myAdd: (baseValue: number, increment: number) => number =
    function(x: number, y: number): number{ return x + y};

只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否正确。

第二个部分是返回值类型。对于返回值,我们在函数和返回值类型之前使用( => )符号,使之清晰明了。返回值类型是函数必要部分,如果函数没有任何返回值,也必须指定返回值类型为void而不能留空。

函数类型只是由参数类型和返回值类型组成的。函数中使用的捕获变量不会体现在类型里。实际上,这些变量是函数的隐藏状态并不是组成API的一部分。

判断类型

// 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里的每个函数都是必须的。这不是指不能传递null或undefined作为参数,而是说编译器检查用户是否每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。简短地说,传递给一个函数的参数个数必须与函数期望的参数个数是一致的。

function buildName(firstName: string, lastName: string){
    return firstName +  " " +lastName;
}
let result1 = buildName("Bob"); //error
let result2 = buildName("Bob", "Adams", "St."); // error
let result3 = buildName("Bob", "Adams"); //right

JavaScript里,每个参数都是可选的,可传可不传。没有传值的时候就是Undefined。在TypeScript里我们可以再参数旁使用 ? 实现可选参数功能。比如,我们想让last name是可选的:

function buildName(firstName: string, lastName?: string){
    if(lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
let result1 = buildName("Bob"); //right;
let result2 = buildName("Bob", "Adams", "St."); //error
let result3 = buildName("Bob", "Adams"); //right;

可选参数必须跟在必须参数后面。如果上例我们想让first name是可选的,那么久必须调整它们的位置,把first name放在后面。

在TypeScript里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递这个参数值是undefined时。它们叫做默认初始化值的参数。让我们修改上例,把last name的默认值设置为"Smith".

function buildName(firstName: string, lastName = "Smith"){
    return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // Bob Smith
let result2 = buildName("Bob", "Adams", "Sr."); //error
let result3 = buildName("Bob", undefined); //Bob Smith
let result4 = buildName("Bob", "Adams"); //Bob Adams

在所有必须参数后面带默认初始化的参数都是可选的,与可选参数一样,在调用函数的时候可以省略。也就是说可选参数与默认参数共享参数类型。

function buildName(firstName: string, lastName?: string){
    //...
}
function buildName(firstName: string, lastName = "Smith"){
    //...
}

共享同样的类型(firstName:  string, lastName?:  string) => string。默认参数的默认值消失了,只保留了它是一个可选参数的信息。

与普通可选参数不同的是, 带默认值的参数不需要放在必须参数的后面。如果带默认值的参数出现在必须参数前面,用户必须明确的传入undefined值来获取默认值。例如,我们重写最后一个例子,让firstName是带默认值的参数:

function buildName(firstName = "Will", lastName: string){
    return firstName + " " + lastName;
}
let result1 = buildName("Bob"); //error
let result2 = buildName("Bob", "Adams", "Sr."); //error
let result3 = buildName("Bob", "Adams"); //Bob Adams
let result4 = buildName(undefined, "Adams"); //Will Adams

剩余参数

必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。有时,我们想同时操作多个参数,或者并不知道有多少参数传进了。在JavaScript里,可以使用arguments来访问所有传入的参数。

在TypeScript里,可以把所有参数搜集到一个变量里:

function buildName(firstName: string, ...restOfName: string[]){
    return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "Mackinzie"); //Joseph Samuel Lucas Mackinzie

 剩余参数会被当做个数不限的可选参数。可以一个都没有,同样也可以任意个,编译器创建参数数组,名字是省略号(...)后面给定的名字,可以再函数体内使用这个数组。

这个省略号也会在带有剩余参数的函数类型上定义上用到:

function buildName(firstName: string, ...resOfName: string[]){
    return firstName + " " + resOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

this

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被设置成了windows而不是deck对象。因为我们只是独立的调用cardPicker()。顶级的非方法调用会将this视为window(注意在严格模式this指向undefined)。

为了解决这个问题,我们可以再函数返回时就绑定好this。这样的话,无论之后怎么调用都会绑定到deck对象。改变函数表达式使用es6箭头函数语法。箭头函数能保存函数创建时的this值,而不是调用时的值:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamons"],
    cards: Array(52),
    createCardPicker: function(){
        return () =>{
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);
            return { suit: this.suits[pickedSuit], card: pickedCard % 13}
        }    
    }
}
let cardPicked = deck.createCardPicker();
let pickedCard = cardPicked();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);

更好的事情是,TypeScript警告你犯了一个错误,如果给编译器设置了 --noImplicitThis标记。它会支出this.suit[pickedSuit]里的this的类型为any。

this参数