VDone Demo VDone Demo
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Nikolay Tuzov

Backend Developer
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • 初识 TypeScript

  • TypeScript 常用语法

    • Basic Types
    • Variable Declarations
    • Interfaces
    • Classes
    • Functions
      • Basic Examples
      • Function Types
        • Defining Types for Functions
        • Writing the Full Function Type
        • Inferring Types
      • Optional and Default Parameters
        • Rest Parameters
      • this
        • this and Arrow Functions
        • this Parameters
        • this Parameters in Callbacks
      • Overloads
    • Generics
    • Type Inference
    • Advanced Types
  • ts-axios 项目初始化

  • ts-axios 基础功能实现

  • ts-axios 异常情况处理

  • ts-axios 接口扩展

  • ts-axios 拦截器实现

  • ts-axios 配置化实现

  • ts-axios 取消功能实现

  • ts-axios 更多功能实现

  • ts-axios 单元测试

  • ts-axios 部署与发布

  • 《TypeScript 从零实现 axios》
  • TypeScript 常用语法
HuangYi
2020-01-05
Contents

Functions

# Functions

Functions are the foundation of any JavaScript application. They help you implement abstraction layers, simulate classes, provide information hiding, and create modules. In TypeScript, although classes, namespaces, and modules are already supported, functions remain the primary place to define behavior. TypeScript adds extra capabilities to JavaScript functions, making them easier to work with.

# Basic Examples

Just like JavaScript, TypeScript functions can be created as both named functions and anonymous functions. You can choose whichever approach suits your application, whether you're defining a series of API functions or a one-off function.

The following examples quickly recall both types of functions in JavaScript:

// Named function
function add(x, y) {
  return x + y
}

// Anonymous function
let myAdd = function(x, y) {
  return x + y;
}
1
2
3
4
5
6
7
8
9

In JavaScript, functions can use variables from outside the function body. When a function does this, we say it 'captures' those variables. The reasons why this works and the trade-offs involved are beyond the scope of this article, but a deep understanding of this mechanism is very helpful for learning JavaScript and TypeScript.

let z = 100

function addToZ(x, y) {
  return x + y + z
}
1
2
3
4
5

# Function Types

# Defining Types for Functions

Let's add types to the function above:

function add(x: number, y: number): number {
  return x + y
}

let myAdd = function(x: number, y: number): number {
  return x + y
}
1
2
3
4
5
6
7

We can add types to each parameter and then add a return type to the function itself. TypeScript can automatically infer the return type from the return statements.

# Writing the Full Function Type

Now that we've typed the function, let's write the full type of the function.

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

1
2
3
4
5

A function type has two parts: parameter types and return type. Both parts are required when writing out the full function type. We write out the parameter types like a parameter list, specifying a name and type for each parameter. The name is just for readability. We could also write it like this:

let myAdd: (baseValue: number, increment: number) => number =
function(x: number, y: number): number {
  return x + y
}
1
2
3
4

As long as the parameter types match, it's considered a valid function type, regardless of whether the parameter names are correct.

The second part is the return type. We use a fat arrow (=>) between the parameters and the return type to make it clear. As mentioned, the return type is a required part of the function type. If the function doesn't return anything, you must specify void instead of leaving it empty.

A function's type is made up of only the parameter types and return type. Captured variables used within the function are not reflected in the type. In effect, these variables are the function's hidden state and are not part of the API.

# Inferring Types

When trying these examples, you'll find that if you specify the type on one side of the assignment but not the other, the TypeScript compiler will automatically figure out the type:

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

let myAdd: (baseValue: number, increment: number) => number =
function(x, y) {
  return x + y
}
1
2
3
4
5
6
7
8

This is called "contextual typing," a form of type inference. It helps us better specify types for our programs.

# Optional and Default Parameters

Every function parameter in TypeScript is assumed to be required. This doesn't mean you can't pass null or undefined, but rather that the compiler checks whether the user has provided a value for each parameter. The compiler also assumes that only these parameters will be passed to the function. In short, the number of arguments passed to a function must match the number the function expects.

function buildName(firstName: string, lastName: string) {
    return firstName + ' ' + lastName;
}

let result1 = buildName('Bob')                  // Error, too few parameters
let result2 = buildName('Bob', 'Adams', 'Sr.');  // Error, too many parameters
let result3 = buildName('Bob', 'Adams');         // OK
1
2
3
4
5
6
7

In JavaScript, every parameter is optional -- you can pass them or not. When not passed, their value is undefined. In TypeScript, we can use ? next to the parameter name to make it optional. For example, let's make lastName optional:

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

let result1 = buildName('Bob');  // works correctly now
let result2 = buildName('Bob', 'Adams', 'Sr.')  // Error, too many parameters
let result3 = buildName('Bob', 'Adams')  // OK
1
2
3
4
5
6
7
8
9
10

Optional parameters must follow required parameters. If we wanted firstName to be optional, we'd need to rearrange them and put firstName at the end.

In TypeScript, we can also provide a default value for a parameter when the user doesn't pass it or passes undefined. These are called default-initialized parameters. Let's modify the above example to set lastName's default value to "Smith".

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

let result1 = buildName('Bob')                  // returns "Bob Smith"
let result2 = buildName('Bob', undefined)     // works, also "Bob Smith"
let result3 = buildName('Bob', 'Adams', 'Sr.')  // Error, too many parameters
let result4 = buildName('Bob', 'Adams')        // OK
1
2
3
4
5
6
7
8

Unlike plain optional parameters, default-initialized parameters don't need to come after required parameters. If a default-initialized parameter comes before a required parameter, the user must explicitly pass undefined to get the default value. For example, let's rewrite the last example with firstName having a default value:

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

let result1 = buildName('Bob')                  // Error, too few parameters
let result2 = buildName('Bob', 'Adams', "Sr.")  // Error, too many parameters
let result3 = buildName('Bob', 'Adams')         // OK, returns "Bob Adams"
let result4 = buildName(undefined, 'Adams')     // OK, returns "Will Adams"
1
2
3
4
5
6
7
8

# Rest Parameters

Required parameters, default parameters, and optional parameters all have one thing in common: they each represent a single parameter. Sometimes you want to work with multiple parameters at once, or you don't know how many parameters will be passed. In JavaScript, you can use arguments to access all passed arguments.

In TypeScript, you can gather all parameters into a single variable:

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

let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie')
1
2
3
4
5

Rest parameters are treated as an unlimited number of optional parameters. You can have none, or you can have as many as you want. The compiler builds an array with the name you give after the ellipsis (...), which you can use within the function body.

The ellipsis is also used in the function type definition for rest parameters:

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

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName
1
2
3
4
5

# this

Learning how to correctly use this in JavaScript is like a rite of passage. Since TypeScript is a superset of JavaScript, TypeScript programmers also need to understand how this works and identify bugs when they occur. Fortunately, TypeScript can notify you when you use this incorrectly. If you want to understand how this works in JavaScript, first read Yehuda Katz's Understanding JavaScript Function Invocation and "this" (opens new window). Yehuda's article explains the inner workings of this in detail, so we'll only give a brief overview here.

# this and Arrow Functions

In JavaScript, the value of this is set when the function is called. This is a powerful and flexible feature, but you need to take the time to understand the calling context. However, as we all know, this isn't always simple, especially when returning a function or passing a function as an argument.

Let's look at an example:

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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

You can see that createCardPicker is a function that returns another function. If we try to run this program, we'll find that it produces an error instead of the expected output. This is because the this inside the function returned by createCardPicker is set to global instead of the deck object. This happens because we just call cardPicker() on its own. A top-level non-method call like this uses global as this.

To fix this, we can bind the correct this when the function is returned. This way, no matter how it's used later, it will always reference the bound deck object. We need to change the function expression to use ECMAScript 6 arrow syntax. Arrow functions capture the this value at the time of creation rather than at the time of invocation:

let deck = {
  suits: ['hearts', 'spades', 'clubs', 'diamonds'],
  cards: Array(52),
  createCardPicker: function() {
    // NOTE: using arrow function here
    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()

console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# this Parameters

In the above example, this.suits[pickedSuit] is of type any because this comes from a function expression inside an object literal. The fix is to provide an explicit this parameter. this parameters are fake parameters that appear first in the parameter list:

function f(this: void) {
  // make sure "this" is unusable in this standalone function
}
1
2
3

Let's add some interfaces, Card and Deck, to make type reuse clearer and simpler:

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),
  // NOTE: The function now explicitly specifies that its callee must be of type 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()

console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Now TypeScript knows that createCardPicker expects to be called on a Deck object. That is, this is of type Deck, not any.

# this Parameters in Callbacks

You may have also seen this errors in callbacks, when you pass a function to some library function that later calls it. Because when the callback is called, it will be called as a regular function, so this will be undefined. With some work, you can use this parameters to prevent errors. First, the library author needs to annotate the callback type with this:

interface UIElement {
  addClickListener(onclick: (this: void, e: Event) => void): void
}
1
2
3

this: void means that addClickListener expects onclick to be a function that does not require a this.

interface UIElement {
  addClickListener (onclick: (this: void, e: Event) => void): void
}

class Handler {
  type: string

  onClickBad (this: Handler, e: Event) {
    this.type = e.type
  }
}

let h = new Handler()

let uiElement: UIElement = {
  addClickListener () {
  }
}

uiElement.addClickListener(h.onClickBad) // error!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

With this typed, you explicitly declare that onClickBad must be called on an instance of Handler. Then TypeScript detects that addClickListener requires a function with this: void. Change the this type to fix this error:

class Handler {
  type: string;

  onClickBad (this: void, e: Event) {
    console.log('clicked!')
  }
}

let h = new Handler()

let uiElement: UIElement = {
  addClickListener () {
  }
}

uiElement.addClickListener(h.onClickBad)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Because onClickGood specifies this as void, it's valid to pass it to addClickListener. Of course, this also means you can't use this.info. If you want both, you'll have to use an arrow function:

class Handler {
  type: string
  onClickGood = (e: Event) => {
    this.type = e.type
  }
}
1
2
3
4
5
6

This works because arrow functions don't capture this, so you can always pass them to functions expecting this: void.

# Overloads

JavaScript is inherently a very dynamic language. It's quite common for a JavaScript function to return different types of data based on the arguments passed in.

let suits = ['hearts', 'spades', 'clubs', 'diamonds']

function pickCard(x): any {
  if (Array.isArray(x)) {
    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)];
console.log('card: ' + pickedCard1.card + ' of ' + pickedCard1.suit)

let pickedCard2 = pickCard(15)
console.log('card: ' + pickedCard2.card + ' of ' + pickedCard2.suit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

The pickCard method returns two different types depending on the arguments passed. If you pass an array of objects representing cards, the function picks one. If the user wants to pick a card, we tell them what card they picked. But how do we express this in the type system?

The approach is to supply multiple function type definitions for the same function to achieve function overloading. The compiler processes function calls based on this list. Let's overload the pickCard function.

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 (Array.isArray(x)) {
    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)];
console.log('card: ' + pickedCard1.card + ' of ' + pickedCard1.suit)

let pickedCard2 = pickCard(15)
console.log('card: ' + pickedCard2.card + ' of ' + pickedCard2.suit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

With this change, the overloaded pickCard function performs correct type checking when called.

For the compiler to pick the correct type check, it follows a process similar to JavaScript. It walks through the overload list and tries the first overload definition. If it matches, it uses that definition. Therefore, when defining overloads, always put the most specific definition first.

Note that function pickCard(x): any is not part of the overload list, so there are only two overloads here: one that takes an object array and one that takes a number. Calling pickCard with any other arguments produces an error.

Edit (opens new window)
#TypeScript
Last Updated: 2026/03/21, 12:14:36
Classes
Generics

← Classes Generics→

Recent Updates
01
How I Discovered Disposable Email — A True Story
06-12
02
Animations in Grid Layout
09-15
03
Renaming a Git Branch
08-11
More Articles >
Theme by VDone | Copyright © 2026-2026 Nikolay Tuzov | MIT License | Telegram
  • Auto
  • Light Mode
  • Dark Mode
  • Reading Mode