前言
- 函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块,在TypeScript里,虽然已经支持类,命名空间和模块,但
函数仍然是主要的定义行为的地方。
😸简单入门
以下用一个非常非常简单的函数来说明函数包含哪些东西
// 函数声明式
function add(x,y) {
return x + y;
}
// 函数表达式
var myAdd = function (x,y) {
return x + y;
};
以上有两个函数(上面两个函数的相同点为有相同数量的参数,并且返回值为两者相加)
函数声明式:函数声明后不会立即执行,只是在初始化的时候会将函数声明提升,会在我们需要的时候调用到。
函数表达式:实际上是一个匿名函数(函数没有名称),函数存储在变量中,不需要函数名称,通常通过变量名来调用。
下面进入重点,我们对上面的函数进行改造,为其添加类型
function add(x:number,y:number): number {
return x + y;
};
以上我们为参数和返回值添加了类型,当TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。
我们再写一个完整完整函数类型
let myAdd: (x:number, y:number) => number = function(x: number, y: number): number {
return x+y;
};
函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的。
只要 参数类型 是 匹配 的,那么就认为它是有效的函数类型,而不在乎参数名是否正确。
第二部分是返回值类型。 对于返回值,我们在函数和返回值类型之前使用(=>) 符号,使之清晰明了。
推断类型 下面这个这个例子为什么展示的是,你在赋值语句的一边指定了类型但是另一边没有类型的话,TypeScript编译器会自动识别出类型:
let myAdd = function (x:number,y:number):number {
return x + y;
}
let myAdd:(baseValue:number,increment:number) => number = function (x,y) {
return x + y;
};
😸可选参数和默认参数
TypeScript里的每个函数参数都是必须的。 这不是指不能传递 null 或 undefined 作为参数,而是说编译器检查用户是否为每个参数都传入了值。 编译器还会假设只有这些参数会被传递进函数。 简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。
function buildName(firstName:string,lastName:string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error
let result2 = buildName("Bob", "Adams", "Sr."); // error
let result3 = buildName("Bob", "Adams"); // success
在我们平时写JS的时候,我们参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。
但是呢,TypeScript不一样,我们来看一下他的语法:参数名旁使用 ? 实现可选参数的功能。
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob");
let result2 = buildName("Bob", "Adams", "Sr."); // error
let result3 = buildName("Bob", "Adams");
可选参数必须 跟在 必须参数后面。 如果上例我们想让first name是可选的,那么就必须调整它们的位置,把first name放在后面。
设置默认参数 我们也可以为参数提供一个默认值,当用户没有传递这个参数或传递的值是
undefined时。它们叫做有默认初始化值的参数。 让我们修改上例,把last name的默认值设置为"张三"。
function buildName(firstName: string, lastName = "张三") {
return firstName + " " + lastName;
}
在所有必须参数后面的带默认初始化的参数都是可选的,与可选参数一样,在调用函数的时候可以省略。 也就是说可选参数与末尾的默认参数共享参数类型。
function buildName(firstName: string, lastName?: string) {
}
function buildName(firstName: string, lastName = "Smith") {
}
以上两张图片,我们可以看见两个函数共享同样的类型 (firstName: string, lastName?: string) => string 。
❤️🔥 Tips: 与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。位置不做要求
如果 带默认值的参数 出现在必须参数 前面,用户必须 明确 的 传入 undefined 值来获得默认值。
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error
let result2 = buildName("Bob", "Adams", "Sr."); // error
let result3 = buildName("Bob", "Adams"); // ok
let result4 = buildName(undefined, "Adams"); // ok
result1缺少第二个必须参数,result2多出一个参数
😸剩余参数
必要参数, 默认参数 , 可选参数 有个共同点:它们表示某一个参数。
有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在JavaScript里,你可以使用 arguments来访问所有传入的参数。
在TypeScript里,你可以把所有参数收集到一个变量里:
function buildName(firstName:string,...restOfName:string[]) {
}
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。
编译器创建参数数组,名字是你在省略号( ...)后面给定的名字,你可以在函数体内使用这个数组。
这个省略号也会在带有剩余参数的函数类型定义上使用到:
function buildName(firstName:string,...restOfName:string[]){
return firstName + " " + restOfName.join(" ");
};
let buildNameFun:(fname:string,...rest:string[]) => string = buildName;
😹this(本文重头戏)
直接例子接入 下面看一个例子:
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();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
好!大家可以先不要急着往下,先想一下上面会输出什么?
-----------------------------------------------------分割线---------------------------------------------------- 我们尝试去执行这个程序的时候,发现报错了。
想必大家对上面的这个报错非常熟悉,
无法读取未定义的属性。
为什么会这样子呢?
因为 createCardPicker 返回的函数里的 this 被设置成了 window 而不是 deck 对象。 因为我们只是独立的调用了 cardPicker() 。 顶级的非方法式调用会将 this视为window。 (注意:在严格模式下, this为undefined而不是window)。
解决方法:
我们可以在函数被返回时就绑好正确的this。 这样的话,无论之后怎么使用它,都会引用绑定的‘deck’对象。 我们需要改变函数表达式来使用ECMAScript 6(ES6)箭头语法。 箭头函数能保存函数创建时的 this值,而不是调用时的值:
var deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
更好事情是,TypeScript会警告你犯了一个错误,如果你给编译器设置了--noImplicitThis标记。 它会指出 this.suits[pickedSuit]里的 this 的类型为 any。
😹this 参数
不幸的是,this.suits[pickedSuit]的类型依旧为any。 这是因为 this来自对象字面量里的函数表达式。 修改的方法是,提供一个显式的 this参数。 this参数是个假的参数,它出现在参数列表的最前面:
function f(this: void) { //确保“this”在此独立函数中不可用 }
让我们往例子里添加一些接口,Card 和 Deck,让类型重用能够变得清晰简单些:
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this:Deck): () => Card;
}
var deck: Deck = {
suits:[],
cards:[],
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知道createCardPicker期望在某个Deck对象上调用。 也就是说 this是Deck类型的,而非any
😹this参数在回调函数里
你可能看到过在回调函数里的this报错,当你将一个函数传递到某个库函数里稍后会被调用时。 因为当回调被调用的时候,它们会被当成一个普通函数调用, this将为undefined。 稍做改动,你就可以通过 this参数来避免错误。 首先,库函数的作者要指定 this的类型:
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
info: string;
onClickBad(this: Handler, e: Event){
this.info = e.type;
}
onClickGood(this: void, e: Event) {
console.log("say hello");
}
}
let h = new Handler();
let uiElement: UIElement;
uiElement.addClickListener(h.onClickBad); // error
uiElement.addClickListener(h.onClickGood); // correct
指定了 this 类型后,你显式声明 onClickBad 必须在 Handler 的实例上调用。 然后TypeScript会检测到 addClickListener 要求函数带有 this: void 。改变 this类型来修复这个错误:成员变量 onClickGood。
因为onClickGood指定了this类型为void,所以传递addClickListener是合法的。 这也意味着不能使用 this.info。 如果你两者都想要,你不得不使用箭头函数了:
class Handlers {
info: string;
onClickGood = (e: Event) => { this.info = "xxx" }
}
这是可行的,因为箭头函数不会捕获this,所以你总是可以把它们传给期望this: void的函数。 缺点是每个 Handler对象都会创建一个箭头函数。 另一方面,方法只会被创建一次,添加到 Handler的原型链上。 它们在不同 Handler对象间是共享的。
😺重载
JavaScript本身是个动态语言。 JavaScript里函数根据传入不同的参数而返回不同类型的数据是很常见的。
方法是为同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用。 下面我们来重载
pickCard函数。
let 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 {
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
} else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
这样改变后,重载的pickCard函数在调用的时候会进行正确的类型检查。
为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
注意,function pickCard(x): any并不是重载列表的一部分,因此这里只有两个重载:一个是接收对象另一个接收数字。 以其它参数调用 pickCard会产生错误。
😸总结
-
首先非常感谢掘金的各位小伙伴的阅读,本人在掘金开启自己的学习记录分享😄(这也是我在掘金的第三篇博客)。
-
上文如果描述有错误的,欢迎指正哦。也欢迎各位小伙伴一起交流学习,分享工作经验😃。
-
本文是依照TypeScript官网 + 自己的语言描述结合写出来的。希望对各位刚开始学TypeScript有帮助哦 🥰!
-
本文仅用作学习用途