TypeScript手册翻译系列5-函数

函数

函数是JavaScript中任意应用程序的基本构件块。能够在函数基础上创建抽象层,模拟类,信息隐藏,模块。在TypeScript中,虽然已经有类和模块,但函数函数仍然扮演着如何作事的关键角色。TypeScript还在标准JavaScript 函数基础上增长了一些新的能力,来使得函数更容易使用。
javascript

函数

TypeScript与JavaScript同样,均可以建立命名函数,也能够建立匿名函数。这样容许开发人员根据应用程序选择最合适的方式,不管是在一个API中创建一列函数仍是创建一个one-off函数来转向另外一个函数。

先快速看下JavaScript中这两种方法是什么样的:
java

// Named function-命名函数
function add(x, y) {    
    return x+y;
}

//Anonymous function-匿名函数
var myAdd = function(x, y) { return x+y; };


就像JavaScript中同样,函数能够返回变量。当返回变量时就称‘捕获’这些变量。理解这是如何工做,以及使用该技术时须要注意哪些事项,虽然超出了本片文章范围,但要掌握JavaScript和TypeScript语言,就须要对该机制可以有透彻理解。
typescript

var z = 100;

function addToZ(x, y) {    
    return x+y+z;
}

函数类型

Typing the function

咱们对前面的例子添加类型:
编程

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

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

咱们对每一个参数添加类型,而后对函数返回值添加类型。TypeScript能够经过查看返回语句算出返回类型,因此在许多状况下也能够选择不添加返回类型。
数组

Writing the function type

如今咱们已经对函数添加了类型,如今咱们写出函数的完整类型,来看看函数类型的每一部分。
dom

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

一个函数的类型有相同的两部分:参数类型和返回类型。当写下全部函数类型时,这两部分都须要写出。参数类型就像一个参数列表同样,每一个参数有一个名称和一个类型。名称只是有助于可读性。也能够写为:
函数

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

只要函数的参数类型一一对应,就认为是有效类型,而不用考虑参数名称是否相同。

第二部分是返回类型。咱们在参数和返回类型之间使用fat arrow (=>)使返回类型更为清晰。就像前面提到的,这是函数类型所需的一部分,因此函数若是没有返回值,应当用'void'而不是什么也不填写。

注意,只有参数类型和返回类型组成了函数类型。捕获的变量并不反映在类型中。实际上,捕获变量是函数‘隐藏状态’的一部分,不是API的一部分。

学习

Inferring the types

下面例子中,你会注意到若是在等式的一边有类型而另外一边没有类型时,TypeScript编译器能够算出类型:
ui

// myAdd has the full function type
// myAdd有完整的函数类型
var myAdd = function(x: number, y: number): number { return x+y; };

// The parameters 'x' and 'y' have the type number
// 参数'x' 与 'y'的类型是number
var myAdd: (baseValue:number, increment:number)=>number = 
    function(x, y) { return x+y; };


这被称为'contextual typing'(上下文类型推断),一种类型推断形式。这有助于减小程序中须要键入的类型。
this

可选参数与缺省参数

与JavaScript不一样,在TypeScript中认为函数的每一个参数都是必须的。这并不表示参数取值不是为'null',而是当编译器调用函数时将检查每一个参数的值。编译器还假定只有这些参数传递给函数。简言之,输入的函数参数数量必须与函数期待的参数数量相同。

function buildName(firstName: string, lastName: string) {    
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  //error, too few parameters
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right


在JavaScript中,每一个参数都被视为可选参数,用户能够不填充参数,此时这些未填充的参数自动取值为undefined。在TypeScript中能够在可选参数旁边用'?'来实现相同功能。例如但愿last name为可选参数:

function buildName(firstName: string, lastName?: string) {    
    if (lastName)        
        return firstName + " " + lastName;    
    else
        return firstName;
}

var result1 = buildName("Bob");  //works correctly now
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right


可选参数必须跟在必选参数后面。假定想要使first name而不是last name为可选参数,就须要改变函数参数顺序,将first name参数放在后面。

在TypeScript中,当用户没有对可选参数提供值时能够事先设置一个值,这些参数也被称为缺省参数。之前面例子举例,设置last name的缺省值为"Smith"。

function buildName(firstName: string, lastName = "Smith") {    
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  //works correctly now, also
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right


就像可选参数同样,在参数列表中缺省参数也必须放在必选参数后面。

可选参数和缺省参数都拥有相同的类型:

function buildName(firstName: string, lastName?: string) {


function buildName(firstName: string, lastName = "Smith") {

有相同的类型 "(firstName: string, lastName?: string)=>string",缺省参数对应的缺省值消失了,只剩下可选参数。

Rest参数

必选参数,可选参数,缺省参数都有同样相同:一次只涉及一个参数。有时候但愿将多个参数归为一组,或不清楚函数最终会传入多少个参数。在JavaScript中,能够用函数体内可见的可变参数来表示。

在TypeScript中,能够将这些参数组合成一个变量:

function buildName(firstName: string, ...restOfName: string[]) {	
    return firstName + " " + restOfName.join(" ");
}

var employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

Rest参数被看作无穷数量个可选参数。用户能够不输入参数,或根据实际状况输入N个参数。编译器将函数中在省略号...后面的参数名称用于构建一个参数数组,这样能够在函数中使用。


省略号...也用在函数rest参数的类型中:

function buildName(firstName: string, ...restOfName: string[]) {	
    return firstName + " " + restOfName.join(" ");
}

var buildNameFun: (fname: string, ...rest: string[])=>string = buildName;

Lambdas及使用'this'

在JavaScript函数中'this'如何工做是学习JavaScript编程人员常见的问题。事实上,学习如何使用它就像是开发人员对JavaScript愈来愈驾轻就熟的一种成长仪式。因为TypeScript是JavaScript的一个超集,TypeScript开发人员也须要学习如何使用'this',当没有正确使用时须要知道如何解决。在JavaScript中能够写一整片文章描述如何使用'this',并且已经有许多文章。这里主要看一些基本内容。


在JavaScript中,当调用函数时设置'this'变量。这个特性很强大并且灵活,但代价是老是必须知道函数执行的上下文。众所周知,这会致使混乱,例如当函数被用做回调函数时。

下面看一个例子:

var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {        
        return function() {            
            var pickedCard = Math.floor(Math.random() * 52);            
            var pickedSuit = Math.floor(pickedCard / 13);			
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);


若是运行这个例子,会获得一个错误而不是预期的alert box。这是由于函数中用到的'this'是由'createCardPicker'建立的,它被设置为'window'而不是'deck'对象。当调用'cardPicker()'时就会发生,这里'this'除了Window之外没有动态绑定。(备注:在严格模式下,this将等于undefined而不是window)。

能够在函数返回前将函数绑定到正确的'this'变量来修复该问题。这样不用考虑函数在后面如何使用,就可以看到最初的'deck' 对象。

为了修复问题,咱们用lambda语法( ()=>{} )而非JavaScript函数表达式来表示函数。这样当函数建立时就自动捕获'this'而不是在函数被调用时捕获:

var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {        
        // Notice: the line below is now a lambda, allowing us to capture 'this' earlier
        return () => {            
            var pickedCard = Math.floor(Math.random() * 52);            
            var pickedSuit = Math.floor(pickedCard / 13);			
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);


更多讨论'this'的信息,可参见Yahuda Katz的Understanding JavaScript Function Invocation and “this”

译者注:这篇参考文章的核心思想:

fn(...args)等同于fn.call(window [ES5-strict: undefined], ...args)

(function() {})()等同于(function() {}).call(window [ES5-strict: undefined)

重载(Overloads)

JavaScript本质上就是一种动态语言。常常能够看到一个JavaScript函数能够基于传递参数的形(shape)返回不一样类型的对象。

var suits = ["hearts", "spades", "clubs", "diamonds"];

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") {        
        var pickedCard = Math.floor(Math.random() * x.length);        
        return pickedCard;
    }    
    // Otherwise just let them pick the card
    else if (typeof x == "number") {        
        var pickedSuit = Math.floor(x / 13);        
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

var myDeck = [{ suit: "diamonds", card: 2 }, 
              { suit: "spades", card: 10 }, 
              { suit: "hearts", card: 4 }];
var pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

var pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);


这里'pickCard'函数根据用户传入的信息返回两个不一样对象。若是用户传入的是表示deck的对象(一副牌),函数就pick the card(从中挑选一张牌);若是用户选择一个数字,就告诉用户选择的是什么牌。但类型系统中如何来描述呢?

答案是对同一个函数提供多个函数类型来重载(overloads)。编译器用这个列表来解析函数调用。下面建立一组重载函数,来描述'pickCard'函数接受什么参数,以及返回什么参数。

var 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") {        
        var pickedCard = Math.floor(Math.random() * x.length);        
        return pickedCard;
    }    
    // Otherwise just let them pick the card
    else if (typeof x == "number") {        
        var pickedSuit = Math.floor(x / 13);        
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

var myDeck = [{ suit: "diamonds", card: 2 }, 
              { suit: "spades", card: 10 }, 
              { suit: "hearts", card: 4 }];
var pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

var pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);


这样修改后,重载就能够在调用'pickCard'函数时作类型检查。

为了让编译器选出正确的类型检查,须要遵循相似底层JavaScript的过程。它查看重载列表,对第一个重载尝试用提供的参数来调用函数。若是找到匹配函数,就选择出这个重载函数。所以对重载函数一般按照最具体到最不具体的顺序来排序。

注意'function pickCard(x): any'代码片断不是重载列表,这里只有两个重载函数:一个函数接受对象,一个函数接受一个数字。调用'pickCard'时传入其余类型参数会致使错误。

参考资料

[1] http://www.typescriptlang.org/Handbook#functions

[2] TypeScript系列1-简介及版本新特性, http://my.oschina.net/1pei/blog/493012

[3] TypeScript手册翻译系列1-基础类型, http://my.oschina.net/1pei/blog/493181

[4] TypeScript手册翻译系列2-接口, http://my.oschina.net/1pei/blog/493388

[5] TypeScript手册翻译系列3-类, http://my.oschina.net/1pei/blog/493539

[6] TypeScript手册翻译系列4-模块, http://my.oschina.net/1pei/blog/495948

相关文章
相关标签/搜索