函數_TypeScript筆記5

一.類型

函數的類型分為兩部分:

  • 參數:各個參數的類型

  • 返回值:返回值的類型

例如:

// 具名函數
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 log: (msg: string) => void = function(msg) {
  console.log(msg);
};
// 無參數
let createLogger: () => object = function() {
  return { log };
};
// 既無參數也無返回值
let logUa: () => void = log.bind(this, navigator.userAgent);

P.S.註意到上面示例隻聲明瞭一份類型,是因為右邊匿名函數的類型能夠根據左側類型聲明自動推斷出來,稱之為語境類型推斷(contextual typing)

另外,類型描述裡的參數名隻是可讀性需要,不要求類型描述中的參數名與真實參數名一致,例如:

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

P.S.實際上,還有另一種描述函數類型的方式:接口,具體見接口_TypeScript筆記3

二.參數

可選參數

JavaScript裡參數默認都是可選的(不傳的默認 undefined
),而TypeScript認為每個參數都是必填的,除非顯式聲明可選參數:

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

與可選屬性的語法類似,緊跟在參數名後面的 ?
表示該參數可選,並且要求 可選參數必須出現在必填參數之後
(所以想要 firstName
可選, lastName
必填的話,隻能改變參數順序)

默認參數

默認參數語法與ES規范一致,例如:

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

從含義上看,默認參數當然是可選的(不填就走默認值),因此,可以認為默認參數是特殊的可選參數,甚至連類型描述也是兼容的:

let buildName: (firstName: string, lastName?: string) => string;
// 可選參數
buildName = function(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
};
// 默認參數
buildName = function(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
};

二者類型完全一致,所以, 類型描述並不能完整表達默認參數
(僅能表達出可選的含義,默認值丟失瞭)

另一個區別是,默認參數不必出現在必填參數之後,例如:

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

buildName(undefined, "Adams");

顯式傳入 undefined
占位,具體見三.默認參數

剩餘參數

與ES6不定參數語法一致:

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

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

剩餘參數也是可選的,相當於 不限數量的可選參數

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

另外,類型描述中也采用瞭相同的語法:

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

三.this

this
在JavaScript不那麼容易駕馭,例如:

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

let cat = new Cat('Neko');
// 點擊觸發的log中,name丟瞭
document.body.addEventListener('click', cat.meow);

this的類型

特殊的,TypeScript能夠描述 this
的類型,例如:

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

其中 this
是個 假參數
,並且要求必須作為第一個參數:

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

this
也像普通參數一樣進行類型檢查,能夠提前暴露出類似的錯誤:

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

P.S.另外,可以開啟 --noImplicitThis
編譯選項,強制要求所有 this
必須有顯式的類型聲明

四.重載

類似於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.

(摘自 Types of polymorphism in java- Runtime and Compile time polymorphism

簡言之, 能讓同名函數的不同版本共存
。不同版本體現在參數差異上:

  • 參數數量

  • 參數順序

  • 參數類型

這3個特征中隻要有一個不同就算重載。如果都相同,就認為是重復聲明的方法(Duplicate Method),並拋出編譯錯誤:

// 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裡也有重載的概念,但與Java重載有一些差異,例如:

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

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

看起來非常合理,但在TypeScript裡會報錯:

Duplicate function implementation.

編譯結果是這樣(TypeScript編譯報錯並不影響代碼生成,具體見類型系統):

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

因為JavaScript不支持重載,(同一作用域下的)方法會覆蓋掉先聲明的同名方法,無論函數簽名是否相同。因此,TypeScript裡的 重載能力受限,僅體現在類型上

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

同樣,這些重載類型聲明僅作用於編譯時,因此也有類似於模式匹配的特性:

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

// 這裡value是any類型
let value = sum(1, 2);

上例中先聲明的更寬泛的 any
版本成功匹配,因此並沒有如預期地匹配到更準確的 number
版本,

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.

所以,應該 把最寬泛的版本放到最後聲明

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

參考資料

  • Functions

  • Function Overloads in TypeScript

  • TypeScript function overloading

Via www.ayqy.net

Ref. https://beginnersbook.com/2013/04/runtime-compile-time-polymorphism/, https://www.typescriptlang.org/docs/handbook/functions.html, https://mariusschulz.com/blog/function-overloads-in-typescript, https://stackoverflow.com/questions/13212625/typescript-function-overloading#comment90425722_13212871, https://union-click.jd.com/jdc?d=ZKPlW2

推薦閱讀:

Spread the love

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *