Skip to main content

Functions_TypeScript Notes 5

Free2019-02-01#TypeScript#function overloading typescript#TS函数重载#TypeScript --noImplicitThis#TypeScript模式匹配

There's typed this, and overloading that's not so intuitive

I. Types

Function types are divided into two parts:

  • Parameters: Types of each parameter

  • Return value: Type of the return value

For example:

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

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

Typed function declarations are sufficient to express a function's type information, but cannot be reused. So is there a way to reuse a function's type?

Yes. Extract the type and it can be reused, let's call it type description.

Type Description

Can describe function types using arrow function syntax:

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

Left side of arrow (=>) is parameters and their types, right side is return value type, both are part of syntax structure, cannot be omitted:

// No return value
let log: (msg: string) => void = function(msg) {
  console.log(msg);
};
// No parameters
let createLogger: () => object = function() {
  return { log };
};
// Neither parameters nor return value
let logUa: () => void = log.bind(this, navigator.userAgent);

P.S. Notice in above examples only one type is declared, because the anonymous function on the right side's type can be automatically inferred from the left side type declaration, called contextual typing.

Additionally, parameter names in type description are only for readability, don't require parameter names in type description to match actual parameter names, for example:

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

P.S. Actually, there's another way to describe function types: interfaces, see Interfaces_TypeScript Notes 3 for details.

II. Parameters

Optional Parameters

In JavaScript, parameters are all optional by default (default to undefined if not passed), while TypeScript considers every parameter required, unless explicitly declaring optional parameters:

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

Similar to optional properties syntax, ? immediately after parameter name indicates the parameter is optional, and requires optional parameters must appear after required parameters (so if you want firstName optional, lastName required, can only change parameter order).

Default Parameters

Default parameter syntax is consistent with [ES specification](/articles/默认参数和不定参数-es6 笔记 4/#articleHeader3), for example:

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

From meaning perspective, default parameters are naturally optional (use default value if not filled), therefore, can consider default parameters as special optional parameters, even type descriptions are compatible:

let buildName: (firstName: string, lastName?: string) => string;
// Optional parameters
buildName = function(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
};
// Default parameters
buildName = function(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
};

Both types are completely consistent, so, type descriptions cannot fully express default parameters (can only express optional meaning, default value is lost).

Another difference is, default parameters don't need to appear after required parameters, for example:

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

buildName(undefined, "Adams");

Explicitly pass undefined as placeholder, see [III. Default Parameters](/articles/默认参数和不定参数-es6 笔记 4/#articleHeader3) for details.

Rest Parameters

Consistent with [ES6 rest parameters](/articles/默认参数和不定参数-es6 笔记 4/#articleHeader2) syntax:

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

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

Rest parameters are also optional, equivalent to unlimited number of optional parameters:

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

Additionally, type descriptions also adopt same syntax:

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

III. this

this is not so easy to master in JavaScript, for example:

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

let cat = new Cat('Neko');
// In log triggered by click, name is lost
document.body.addEventListener('click', cat.meow);

Type of this

Specially, TypeScript can describe the type of this, for example:

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);

Where this is a fake parameter, and requires must be first parameter:

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

this also undergoes type checking like ordinary parameters, can expose similar errors in advance:

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

P.S. Additionally, can enable --noImplicitThis compilation option, forcibly require all this must have explicit type declarations.

IV. Overloading

Similar to overloading in 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.

(From Types of polymorphism in java- Runtime and Compile time polymorphism)

In short, allows different versions of same-name functions to coexist. Different versions are reflected in parameter differences:

  • Parameter count

  • Parameter order

  • Parameter type

As long as one of these 3 features is different, it counts as overloading. If all are same, considered duplicate declared method (Duplicate Method), and throws compilation error:

// 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 also has overloading concept, but has some differences from Java overloading, for example:

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

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

Looks very reasonable, but will error in TypeScript:

Duplicate function implementation.

Compilation result is like this (TypeScript compilation errors don't affect code generation, see [Type System](/articles/typescript 简介-typescript 笔记 1/#articleHeader6) for details):

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;
}());

Because JavaScript doesn't support overloading, methods (under same scope) will override previously declared same-name methods, regardless of whether function signatures are same. Therefore, overloading capability in TypeScript is limited, only reflected in types:

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;
}

Similarly, these overload type declarations only act at compile time, therefore also has characteristics similar to [pattern matching](/articles/基础语法-haskell 笔记 1/#articleHeader18):

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

// Here value is any type
let value = sum(1, 2);

In above example, the broader any version declared first successfully matches, therefore didn't match the more accurate number version as expected,

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.

Therefore, should declare the broadest version last:

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

References

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment