之前我们有简单了解了一下使用接口表示函数类型。接下来我们了解 typescript 中的函数和泛型。
函数
//声明式
function add(x, y) {
return x + y;
}
// 函数表达式
let myAdd = function(x, y) {
return x + y;
};
JavaScript 声明方式有声明式和表达式,另外有具名函数和匿名函数:
// 匿名函数
(function() {
let a = 20;
console.log(a);
})();
typescript 除了在 JavaScript 基础上添加了一系列校验外其它没什么很大区别。
typescript 函数类型:
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 myAdd: (baseValue: number, increment: number) => number = function(
x: number,
y: number
): number {
return x + y;
};
这里我们队函数的参数和返回值指定了 number 类型的校验。TypeScript 能够根据返回语句自动推断出返回值类型,因此我们可以省略它。完整函数类型等于号之前的分别为参数和返回值的类型校验,有点类似写箭头函数()=>
,=>箭头代表返回。函数的参数名称不需要和参数类型定义时得名称一致。
let myAdd = function(x: number, y: number): number {
return x + y;
};
// 当指定了参数类型定义但 待校验函数参数没有标注类型时,typescript会自动识别类型
let myAdd: (baseValue: number, increment: number) => number = function(x, y) {
return x + y;
};
当指定了参数类型定义但 待校验函数参数没有标注类型时,typescript 会自动识别类型,按照类型定义规则校验。
JavaScript 里函数的参数是可选的定义之后可传可不传,没有传参的时候是 undefined,例如:
function aa(name, age) {
// do something
}
但是 typescript 里一旦表明这个函数有参数,那么在调用这个函数的时候必须加上参数。如果我们想要添加可选参数,可以使用可选类型:
// 可选参数必须跟在必须参数后面。 如果我们想让first name是可选的,那么就必须调整它们的位置,把first name放在后面
function buildName(firstName: string, lastName?: string) {
if (lastName){
return firstName + " " + lastName;
} else{
else return firstName;
}
}
我们也可以直接像 ES6 一样给参数一个默认值:
// 当调用函数没有传参数lastName时,lastName的默认值为Smith
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入undefined
值来获得默认值——————这里其实意思是(如下面栗子)当我们将默认参数写在必需参数的前面,调用函数只传了一个参数那么这个参数对应的是 firstName,而 lastName 是必需的相当于没有 lastName 的参数,所以报错。所以如果想要Bob
对应 lastName 的值,就需要在第一个参数的位置传入undefined
。
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); //An argument for 'lastName' was not provided.
let result4 = buildName(undefined, "Bob"); // "Will Bob"
对于不知道会有多少个参数的函数,我们也可以像 ES6 一样添加使用扩展符表示:
// 使用... 扩展符,表示可展开的数据
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
在 JavaScript 中,this 的指向与函数被调用的上下文有关系,这使得我们不太好区分。例如:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
suits: 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);
这个例子会报错,我们来解析一下,deck
是一个对象,他包含有 suits、suits
属性和 createCardPicker
方法。createCardPicker
方法返回了一个匿名函数。let cardPicker = deck.createCardPicker()
这句意思是拿到createCardPicker
的返回函数,let pickedCard = cardPicker();
这句的意思是执行匿名函数并且拿到该函数的返回对象。
前面提到了在 JavaScript 中,this 的指向与函数被调用的上下文有关系,在上面的例子中,调用匿名函数的是 window/global,相当于以下函数:
function cardPicker() {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
}
cardPicker();
在 window/global 中找不到suits
对象,所以报错。
这时,JavaScript 提供了一系列改变函数执行上下文的方法:call、apply、bind。我们就可以修改上述例子:
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.call(deck); //手动将cardPicker函数(也就是createCardPicker返回的匿名函数)中的this指向deck对象。
console.log("card: " + pickedCard.card + " of " + pickedCard.suit); // card: 7 of diamonds
也可以在定义这个函数的时候就绑定该函数环境的 this:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
let that = this; // 在这里使用that变量保存 this
return function() {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: that.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit); // card: 12 of spades
对于函数 this 指向问题,ES6 提供了箭头函数,能够能保存函数创建时的 this,被调用的时候不会改变 this 指向:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
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 };
};
}
};
在使用 typescript 的时候,this 默认为 any 类型,如果想要规定 this 的类型,可以写成如下:
// Card接口
interface Card {
suit: string;
card: number;
}
// Deck接口
interface Deck {
suits: string[];
cards: number[];
// 函数类型接口
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// createCardPicker函数 this类型为Deck,返回值类型为Card
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
泛型
平常我们不使用 typescript 进行类型判断的时候,函数的返回值类型与我们传给函数的参数类型不一定相同,可能会因为我们不合理的操作导致返回值类型不是传入值类型。
平常写法,只能传入是 number,返回是 number,灵活性不好:
function identity(arg: number): number {
return arg;
}
使用 any,any 代表接收任意类型,那和不设置 typescript 差不多,传入参数类型值和返回类型值不一定相同:
function identity(arg: any): any {
return arg;
}
为了使返回值的类型与传入参数的类型相同,typescript 可以使用类型变量
来达到目的:
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
类型名称可以不相同:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
此时 T 代表可以是任意类型的数据,但因为 T 可以是任意类型数据,所以得注意如果使用了一些特定类型才有的属性,就会报错:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // error,由于T可以是任意类型数据,有些类型是没有长度属性的,比如number
return arg;
}
数组:
这样传入时数组为任意类型,返回时为传入时的类型,且数组有 length 属性
function foo<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
也可以写成:
function foo<T>(arg: Array<T>): Array<T> {
return arg;
}
类型是一个对象字面量:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: { <U>(arg: U): U } = identity;
泛型接口,和普通接口写起来差不多,只是将类型换成了类型变量:
interface GenericIdentityFn {
<U>(arg: U): U;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
泛型类,和直接写类差不多,类名称后面加个变量类型,其他类型也换成变量类型:
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>(); // 明确GenericNumber接口类型为number
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };