TypeScript 函数完全指南:类型、参数、this 与重载实战
发布时间:2026/9/29 11:34:55来源:尧图网络
文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载本篇指南以 TypeScript 官方手册中文版中《函数》一章为骨架系统讲解函数类型的书写与推断、可选参数与默认参数的规则、剩余参数的收集方式、this的类型安全控制包括箭头函数、this参数与回调场景以及函数重载的声明与解析顺序。读完本篇你将能够为 JavaScript 函数添加精确的类型标注、在事件回调与类方法之间安全传递函数引用并通过重载为同一函数的多形态调用提供编译期保障。函数是 JavaScript 应用程序的基础它帮助你实现抽象层、模拟类、信息隐藏和模块。在 TypeScript 里虽然已经支持类、命名空间和模块但函数仍然是主要的定义行为的地方。TypeScript 为 JavaScript 函数添加了额外的类型系统能力让我们可以更容易、更安全地使用它们。本指南对应仓库中的 zh/handbook/functions.md是该中文手册手册分册的一部分与之配套的还有基础类型、接口、类与泛型等章节。从 JavaScript 函数说起和 JavaScript 一样TypeScript 函数可以创建有名字的函数Named function和匿名函数Anonymous function。你可以随意选择适合应用程序的方式——不论是定义一系列 API 函数还是只使用一次的函数。下面这个例子可以迅速回忆起 JavaScript 中这两种函数形态// 命名函数 function add(x, y) { return x y; } // 匿名函数 let myAdd function (x, y) { return x y; };在 JavaScript 里函数可以使用函数体外部的变量。当函数这么做时我们说它捕获了这些变量let z 100; function addToZ(x, y) { return x y z; }至于为什么可以这样做以及其中的利弊超出了本文的范围但深刻理解这个捕获机制对学习 JavaScript 和 TypeScript 会很有帮助——它在后面的this与闭包话题中还会反复出现。值得留意的是捕获变量是函数的隐藏状态并不会体现在函数类型里详见下文函数类型一节。函数类型为函数定义类型让我们为上面那个函数添加类型。可以给每个参数添加类型之后再为函数本身添加返回值类型function add(x: number, y: number): number { return x y; } let myAdd function (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; };函数类型包含两部分参数类型和返回值类型。当写出完整函数类型的时候这两部分都是需要的。我们以参数列表的形式写出参数类型为每个参数指定一个名字和类型——这个名字只是为了增加可读性也可以这样写let myAdd: (baseValue: number, increment: number) number function ( x: number, y: number ): number { return x y; };只要参数类型是匹配的就认为它是有效的函数类型而不在乎参数名是否正确。第二部分是返回值类型。对于返回值我们在函数和返回值类型之间使用符号使之清晰明了。如之前提到的返回值类型是函数类型的必要部分——如果函数没有返回任何值你也必须指定返回值类型为void而不能留空。一个重要结论是函数的类型只由参数类型和返回值组成函数中使用的捕获变量不会体现在类型里。实际上这些变量是函数的隐藏状态并不是组成 API 的一部分。推断类型尝试这个例子的时候你会注意到就算仅在等式的一侧带有类型TypeScript 编译器仍可正确识别类型// myAdd 拥有完整的函数类型 let myAdd function (x: number, y: number): number { return x y; }; // 参数 x 和 y 被推断为 number 类型 let myAdd: (baseValue: number, increment: number) number function (x, y) { return x y; };这叫做按上下文归类Contextual Typing是类型推论的一种——类型检查器使用等式另一侧期望位置的类型来反向推断函数表达式内部的参数类型。它帮助我们更好地为程序指定类型。关于这一机制的更多说明可以参考本仓库中专门的类型推论章节其中提到上下文归类会发生在函数的参数、赋值表达式的右边、类型断言、对象成员和数组字面量及返回值语句等位置如果函数不在上下文归类的位置上参数将隐式成为any类型除非开启--noImplicitAny。可选参数和默认参数TypeScript 里的每个函数参数都是必须的。这不是指不能传递null或undefined作为参数而是说编译器会检查用户是否为每个参数都传入了值并假设只有这些参数会被传递进函数。简短地说传递给一个函数的参数个数必须与函数期望的参数个数一致function buildName(firstName: string, lastName: string) { return firstName lastName; } let result1 buildName(Bob); // 错误参数太少 let result2 buildName(Bob, Adams, Sr.); // 错误参数太多 let result3 buildName(Bob, Adams); // 正确JavaScript 里每个参数都是可选的可传可不传没传参的时候它的值就是undefined。在 TypeScript 里我们可以在参数名旁使用?实现可选参数的功能。比如让 last name 变成可选function buildName(firstName: string, lastName?: string) { if (lastName) return firstName lastName; else return firstName; } let result1 buildName(Bob); // 现在可以正确工作了 let result2 buildName(Bob, Adams, Sr.); // 错误参数太多 let result3 buildName(Bob, Adams); // 正确可选参数必须跟在必须参数后面。如果想让 first name 是可选的就必须调整它们的位置把 first name 放在后面。在 TypeScript 里我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是undefined时生效。它们叫做有默认初始化值的参数。修改上例把 last name 的默认值设置为Smithfunction buildName(firstName: string, lastName Smith) { return firstName lastName; } let result1 buildName(Bob); // 正确返回 Bob Smith let result2 buildName(Bob, undefined); // 仍然有效也返回 Bob Smith let result3 buildName(Bob, Adams, Sr.); // 错误参数太多 let result4 buildName(Bob, Adams); // 正确在所有必须参数后面的带默认初始化的参数都是可选的与可选参数一样在调用函数的时候可以省略。也就是说可选参数与末尾的默认参数共享参数类型function buildName(firstName: string, lastName?: string) { // ... }和function buildName(firstName: string, lastName Smith) { // ... }共享同样的类型(firstName: string, lastName?: string) string。在函数类型中默认参数的默认值不会显示而只会显示它是一个可选参数。与普通可选参数不同的是带默认值的参数不需要放在必须参数的后面。如果带默认值的参数出现在必须参数前面用户必须显式地传入undefined值来获得默认值。例如重写最后一个例子让firstName是带默认值的参数function buildName(firstName Will, lastName: string) { return firstName lastName; } let result1 buildName(Bob); // 错误参数太少 let result2 buildName(Bob, Adams, Sr.); // 错误参数太多 let result3 buildName(Bob, Adams); // 正确返回 Bob Adams let result4 buildName(undefined, Adams); // 正确返回 Will Adams剩余参数必要参数、默认参数和可选参数有个共同点它们表示某一个参数。有时你想同时操作多个参数或者并不知道会有多少参数传递进来。在 JavaScript 里你可以使用arguments来访问所有传入的参数在 TypeScript 里你可以把所有参数收集到一个变量里function buildName(firstName: string, ...restOfName: string[]) { return firstName restOfName.join( ); } let employeeName buildName(Joseph, Samuel, Lucas, MacKinzie);剩余参数Rest Parameters会被当做个数不限的可选参数——可以一个都没有同样也可以有任意个。编译器会创建参数数组名字是你在省略号...后面给定的名字你可以在函数体内使用这个数组。这个省略号也会在带有剩余参数的函数类型定义上使用到function buildName(firstName: string, ...restOfName: string[]) { return firstName restOfName.join( ); } let buildNameFun: (fname: string, ...rest: string[]) string buildName;this学习如何在 JavaScript 里正确使用this就好比一场成年礼。由于 TypeScript 是 JavaScript 的超集TypeScript 程序员也需要弄清this的工作机制并且当有 bug 的时候能够找出错误所在。幸运的是TypeScript 能在你错误地使用this的地方给出提示。关于 JavaScript 里this的更完整讨论包括典型症状、危险信号以及Function.bind等修复方案的利弊可以参考本仓库的 TypeScript 里的 this 一文。this 和箭头函数JavaScript 里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(); alert(card: pickedCard.card of pickedCard.suit);可以看到createCardPicker是个函数并且它又返回了一个函数。如果尝试运行这个程序会发现它并没有弹出对话框而是报错了——因为createCardPicker返回的函数里的this被设置成了window而不是deck对象我们只是独立地调用了cardPicker()顶级的非方法式调用会将this视为window注意在严格模式下this为undefined而不是window。为了解决这个问题我们可以在函数被返回时就绑好正确的this。这样的话无论之后怎么使用它都会引用绑定的deck对象。我们需要把函数表达式改成 ECMAScript 6 箭头语法。箭头函数能保存函数创建时的this值而不是调用时的值let deck { suits: [hearts, spades, clubs, diamonds], cards: Array(52), createCardPicker: function () { // 注意下面这行现在是箭头函数允许我们在这里就捕获 this return () { 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);更好的事情是如果你给编译器设置了--noImplicitThis标记TypeScript 会警告你犯了错误——它会指出this.suits[pickedSuit]里的this的类型为any。该选项在 编译选项 文档中的说明为当this表达式的值为any类型的时候生成一个错误它也是--strict严格模式选项家族的一员--strict包含--noImplicitAny、--noImplicitThis、--alwaysStrict、--strictBindCallApply、--strictNullChecks、--strictFunctionTypes和--strictPropertyInitialization。--noImplicitThis自 TypeScript 2.0 起加入用来标记函数中所有没有明确类型注释的this的使用详见 TypeScript 2.0 发布说明。this 参数不幸的是this.suits[pickedSuit]中的this的类型依旧为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; } let deck: Deck { suits: [hearts, spades, clubs, diamonds], cards: Array(52), // 注意函数现在显式指定了它的调用者必须为 Deck 类型 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 }; }; }, }; let cardPicker deck.createCardPicker(); let pickedCard cardPicker(); alert(card: pickedCard.card of pickedCard.suit);现在 TypeScript 知道createCardPicker期望在某个Deck对象上被调用。也就是说this是Deck类型的而非any因此--noImplicitThis不会报错了。回调函数里的 this 参数当你将一个函数传递到某个库函数里、在稍后被调用时你可能也见到过回调函数里的this会报错。因为当回调函数被调用时它会被当成一个普通函数调用this将为undefined。稍做改动你就可以通过this参数来避免错误。首先库函数的作者要指定this的类型interface UIElement { addClickListener(onclick: (this: void, e: Event) void): void; }this: void意味着addClickListener期望onclick是一个函数且它不需要一个this类型。然后为调用代码里的this添加类型注解class Handler { info: string; onClickBad(this: Handler, e: Event) { // 糟糕这里使用了 this。使用这个回调会导致运行时崩溃 this.info e.message; } } let h new Handler(); uiElement.addClickListener(h.onClickBad); // 错误指定了this类型后你显式声明onClickBad必须在Handler的实例上调用然后 TypeScript 会检测到addClickListener要求函数带有this: void。改变this类型来修复这个错误class Handler { info: string; onClickGood(this: void, e: Event) { // 这里不能使用 this因为它的类型是 void console.log(clicked!); } } let h new Handler(); uiElement.addClickListener(h.onClickGood);因为onClickGood指定了this类型为void因此传递给addClickListener是合法的当然这也意味着不能使用this.info。如果你两者都想要既能在回调里安全传递又能访问实例状态就不得不使用箭头函数了class Handler { info: string; onClickGood (e: Event) { this.info e.message; }; }这是可行的因为箭头函数使用外层的this所以你总是可以把它们传给期望this: void的函数。缺点是每个Handler对象都会创建一个箭头函数另一方面普通方法只会被创建一次添加到Handler的原型链上在不同Handler对象间是共享的。关于实例箭头函数与原型方法在内存、继承super等方面的完整利弊权衡可以参考 TypeScript 里的 this 中的修复一节。重载JavaScript 本身是个动态语言函数根据传入不同的参数而返回不同类型的数据是很常见的。例如下面的pickCard函数let suits [hearts, spades, clubs, diamonds]; 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方法根据传入参数的不同会返回两种不同的类型如果传入的是代表纸牌的对象数组函数作用是从中随机抓一张牌如果用户传入数字则告诉他抓到了什么牌。问题是这怎么在类型系统里表示呢方法是为同一个函数提供多个函数类型定义来进行函数重载Overloads。编译器会根据这个重载列表去处理函数的调用。下面来重载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 类型系统的方方面面。掌握本篇内容后你可以为任意 JavaScript 函数写出精确的完整函数类型并理解参数名不影响类型匹配、捕获变量不属于 API 类型利用上下文归类减少冗余的类型标注正确处理可选参数、默认参数包括它们共享的类型签名与剩余参数借助箭头函数、显式this参数与--noImplicitThis严格选项在回调、事件监听等场景中避免this丢失导致的运行时错误通过重载让同一函数的不同形态调用获得编译期类型检查。继续深入可以参考本仓库中的相关章节类型推论上下文归类的更多细节、TypeScript 里的 thisthis问题的完整解决方案对比、编译选项--strict、--noImplicitThis等选项说明、类方法与继承中的函数行为以及接口函数类型的接口形态。赞分享文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载相关推荐Pixelle-Video 上手指南输入一个主题5 分钟做出 AI 短视频Pixelle Video 上手指南输入一个主题5 分钟做出 AI 短视频 Pixelle Video 是一款开源的 AI 全自动短视频引擎输入一个主题人工智能AI 应用音视频媒体生成TypeScript 函数类型系统完全指南参数注解、返回类型、可选参数与重载实战TypeScript 函数类型系统完全指南参数注解、返回类型、可选参数与重载实战 函数是任何可组合系统的核心构建块TypeScript 的类型系统也为函数投教程Flow 函数类型标注完全指南参数、返回值、this、重载与可调用对象Flow 函数类型标注完全指南参数、返回值、 this 、重载与可调用对象 在 Flow 中函数是类型系统接触最频繁的形态——几乎所有数据在流入流出程序时都开发工具静态分析代码质量上一篇如何使用Bus Pirate进行SPI协议分析快速上手教程下一篇Granite-3B-Code-Base-2K模型压缩与优化终极指南如何在资源受限环境中高效运行代码生成AI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网