TypeScript系列6-手册-函数

函数

函数是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

时间: 2024-10-09 22:09:16

TypeScript系列6-手册-函数的相关文章

php从入门到放弃系列-03.php函数和面向对象

php从入门到放弃系列-03.php函数和面向对象 一.函数 php真正的威力源自它的函数,内置了1000个函数,可以参考PHP 参考手册. 自定义函数: 1 function functionName() 2 { 3 要执行的代码; 4 } 函数命名的准则: 函数的名称应该提示出它的功能 函数名称以字母或下划线开头(不能以数字开头) 二.面向对象 1.类基础语法: 1 <?php 2 class Site { 3 /* 成员变量 */ 4 var $url; 5 var $title; 6 7

TypeScript系列3-手册-接口

接口 TypeScript的一个核心原则是类型检测重点放在值的形状(shape),这有时候被称为鸭子类型化(duck typing)或结构子类型化(structural subtyping).在TypeScript中,用接口(interfaces)来命名这些类型,来定义项目内部代码的合约以及与外部代码的契约. 第一个接口 理解interface如何工作,最容易的方式就是先看一个简单例子: function printLabel(labelledObj: {label: string}) {   

TypeScript系列4-手册-类

类 传统的JavaScript语言基于函数和原型链继承机制的方式构建可重用的组件,但这对于OO编程人员来说显得比较笨拙,因为是在类的基础上来继承.从JavaScript标准ECMAScript 6开始可以采用面向对象基于类来构建应用.在TypeScript中开发人员现在就可以使用这些技术,TypeScript可以将它们编译为目前大多数浏览器和平台能允许的普通Javascript代码,可以不用等待下一版本的JavaScript的到来. 类 我们先看一个基于类的简单例子: class Greeter

lodash用法系列(3),使用函数

Lodash用来操作对象和集合,比Underscore拥有更多的功能和更好的性能. 官网:https://lodash.com/引用:<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>安装:npm install lodash 首先通过npm安装lodash:npm i --save lodash 在js文件中引用lodash:var _ =

C语言K&R习题系列——使用缓冲区函数接受长字符输入

原题: Write a program to print all input lines that are longer than 80 characters.  ,实现起来不算难,关键是用到了缓冲区,很不错的一种思想! /* Write a program to print all input lines  * that are longer than 80 characters  */    #include < stdio.h >    #define MINLENGTH 81    /

Cisco UCS C系列服务器配置手册之使用WebBIOS配置RDID

Cisco UCS C系列服务器配置手册 重启后在如下图5所示界面时按Ctrl+H进入图6的Raid配置界面. 图5 图6 进入到raid的配置界面,左面导航选择Configuration Wizard,进入图7. 图7 因为第一次做RAID,所以选择New configuration 创建新的raid配置,点击Next,进入图8. 图8 选择Manual Configuration手动安装,然后点击Next进入图9. 图9 将图9左侧的多个硬盘全部加入右侧到Drive Groups中(每选择一

代码收藏系列--javascript--日期函数

/** * 将 Date 转化为指定格式的String * 月(M).日(d).12小时(h).24小时(H).分(m).秒(s).周(E).季度(q) 可以用 1-2 个占位符 * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) * @param Date date * @param string fmt * @returns string */ function formatDate(date, fmt) { //author: meizz var

almost最好的Vue + Typescript系列02 项目结构篇

基于vue-cli 3.x,配合typescript的环境构建的新vue项目,跟以前的结构相比,有了一些变化,下面我们来简单的了解一下 基本结构: node_modules: 项目中安装的依赖模块 public: 主页文件index.html && favicon.icon(将以往单独在外部的index.html移到了public文件夹下),index.html我们可以像平时普通的html文件一样引入文件(css,js)和书写基本信息,添加meta标签等. src: 源码文件夹,基本上我们

TypeScript系列2-手册-基础类型

基础类型(Basic Types) 我们提供一些基础类型,比如数字(numbers).字符串(strings).结构(structures),布尔值(Boolean)等等.在TypeScript中,我们支持JavaScript中相同的类型,还提供枚举类型. Boolean 最基础的数据类型是true/false值,JavaScript和TypeScript以及其他语言把它叫做'boolean'类型. var isDone: boolean = false; Number 跟JavaScript一