函数_TypeScript笔记5

一.类型

函数的类型分为两部分:

  • 参数:各个参数的类型

  • 返回值:返回值的类型

例如:

// 具名函数
function add(x: number, y: number): number {
    return x + y;
}

// 匿名函数
let myAdd = function(x: number, y: number): number { return x + y; };

带类型的函数声明足够表达一个函数的类型信息,但无法复用。那么有办法复用一个函数的类型吗?

有。把类型抽离出来就可以复用了,姑且称之为类型描述

类型描述

可以通过箭头函数语法描述函数的类型:

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

箭头(=>)左侧是参数及其类型,右侧是返回值类型,都是语法结构的一部分,不可缺省

// 无返回值
let log: (msg: string) => void = function(msg) {
  console.log(msg);
};
// 无参数
let createLogger: () => object = function() {
  return { log };
};
// 既无参数也无返回值
let logUa: () => void = log.bind(this, navigator.userAgent);

P.S.注意到上面示例只声明了一份类型,是因为右边匿名函数的类型能够根据左侧类型声明自动推断出来,称之为语境类型推断(contextual typing)

另外,类型描述里的参数名只是可读性需要,不要求类型描述中的参数名与真实参数名一致,例如:

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

P.S.实际上,还有另一种描述函数类型的方式:接口,具体见接口_TypeScript笔记3

二.参数

可选参数

JavaScript里参数默认都是可选的(不传的默认undefined),而TypeScript认为每个参数都是必填的,除非显式声明可选参数:

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

可选属性的语法类似,紧跟在参数名后面的?表示该参数可选,并且要求可选参数必须出现在必填参数之后(所以想要firstName可选,lastName必填的话,只能改变参数顺序)

默认参数

默认参数语法与ES规范一致,例如:

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

从含义上看,默认参数当然是可选的(不填就走默认值),因此,可以认为默认参数是特殊的可选参数,甚至连类型描述也是兼容的:

let buildName: (firstName: string, lastName?: string) => string;
// 可选参数
buildName = function(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
};
// 默认参数
buildName = function(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
};

二者类型完全一致,所以,类型描述并不能完整表达默认参数(仅能表达出可选的含义,默认值丢失了)

另一个区别是,默认参数不必出现在必填参数之后,例如:

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

buildName(undefined, "Adams");

显式传入undefined占位,具体见三.默认参数

剩余参数

ES6不定参数语法一致:

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

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

剩余参数也是可选的,相当于不限数量的可选参数

Rest parameters are treated as a boundless number of optional parameters.

另外,类型描述中也采用了相同的语法:

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

三.this

this在JavaScript不那么容易驾驭,例如:

class Cat {
  constructor(public name: string) {}
  meow() { console.log(`${this.name} meow~`); }
}

let cat = new Cat('Neko');
// 点击触发的log中,name丢了
document.body.addEventListener('click', cat.meow);

this的类型

特殊的,TypeScript能够描述this的类型,例如:

class Cat {
  constructor(public name: string) {}
  meow(this: Cat) { console.log('meow~'); }
}

class EventBus {
  on(type: string, handler: (this: void, ...params) => void) {/* ... */}
}

new EventBus().on('click', new Cat('Neko').meow);

其中this是个假参数,并且要求必须作为第一个参数:

this parameters are fake parameters that come first in the parameter list of a function.

this也像普通参数一样进行类型检查,能够提前暴露出类似的错误:

Argument of type ‘(this: Cat) => void’ is not assignable to parameter of type ‘(this: void, …params: any[]) => void’.

P.S.另外,可以开启--noImplicitThis编译选项,强制要求所有this必须有显式的类型声明

四.重载

类似于Java里的重载:

Method Overloading: This allows us to have more than one method having the same name, if the parameters of methods are different in number, sequence and data types of parameters.

(摘自Types of polymorphism in java- Runtime and Compile time polymorphism

简言之,能让同名函数的不同版本共存。不同版本体现在参数差异上:

  • 参数数量

  • 参数顺序

  • 参数类型

这3个特征中只要有一个不同就算重载。如果都相同,就认为是重复声明的方法(Duplicate Method),并抛出编译错误:

// Java
public class Addition {
  // Compile Time Error - Duplicate method sum(int, int)
  int sum(int a, int b) {
    return a+b;
  }

  // Compile Time Error - Duplicate method sum(int, int)
  void sum(int a, int b) {
    System.out.println(a+b);
  }
}

TypeScript里也有重载的概念,但与Java重载有一些差异,例如:

class Addition {
  sum(a: number, b: number): number {
    return a + b;
  }

  sum(a: number[]): number {
    return a.reduce((acc, v) => acc + v, 0);
  }
}

看起来非常合理,但在TypeScript里会报错:

Duplicate function implementation.

编译结果是这样(TypeScript编译报错并不影响代码生成,具体见类型系统):

var Addition = /** @class */ (function () {
    function Addition() {
    }
    Addition.prototype.sum = function (a, b) {
        return a + b;
    };
    Addition.prototype.sum = function (a) {
        return a.reduce(function (acc, v) { return acc + v; }, 0);
    };
    return Addition;
}());

因为JavaScript不支持重载,(同一作用域下的)方法会覆盖掉先声明的同名方法,无论函数签名是否相同。因此,TypeScript里的重载能力受限,仅体现在类型上

function sum(a: number, b: number): number;
function sum(a: number[]): number;
function sum(a, b?) {
    if (Array.isArray(a)) {
        a.reduce((acc, v) => acc + v, 0);
    }
    return a + b;
}

同样,这些重载类型声明仅作用于编译时,因此也有类似于模式匹配的特性:

function sum(a: any, b: any): any;
function sum(a: number, b: number): number;
function sum(a, b) {
    return Number(a) + Number(b);
}

// 这里value是any类型
let value = sum(1, 2);

上例中先声明的更宽泛的any版本成功匹配,因此并没有如预期地匹配到更准确的number版本,

It looks at the overload list, and proceeding with the first overload attempts to call the function with the provided parameters. If it finds a match, it picks this overload as the correct overload.

所以,应该把最宽泛的版本放到最后声明

it’s customary to order overloads from most specific to least specific.

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code