码迷,mamicode.com
首页 > 其他好文 > 详细

TypeScript系列6-手册-函数

时间:2015-09-05 12:36:35      阅读:218      评论:0      收藏:0      [点我收藏+]

标签:

函数

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

函数

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

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

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

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


就像JavaScript中一样,函数可以返回变量。当返回变量时就称‘捕获’这些变量。理解这是如何工作,以及使用该技术时需要注意哪些事项,虽然超出了本片文章范围,但要掌握JavaScript和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

现在我们已经对函数添加了类型,现在我们写出函数的完整类型,来看看函数类型的每一部分。

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编译器可以算出类型:

// 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‘(上下文类型推断),一种类型推断形式。这有助于减少程序中需要键入的类型。

可选参数与缺省参数

与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系列2-手册-基础类型, http://my.oschina.net/1pei/blog/493181

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

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

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

TypeScript系列6-手册-函数

标签:

原文地址:http://my.oschina.net/1pei/blog/501273

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!