一.類型
函數的類型分為兩部分:
-
參數:各個參數的類型
-
返回值:返回值的類型
例如:
// 具名函數
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 規範](/articles/默認參數和不定參數-es6 筆記 4/#articleHeader3) 一致,例如:
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 佔位,具體見 [三。默認參數](/articles/默認參數和不定參數-es6 筆記 4/#articleHeader3)
剩餘參數
與 [ES6 不定參數](/articles/默認參數和不定參數-es6 筆記 4/#articleHeader2) 語法一致:
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 編譯報錯並不影響代碼生成,具體見 [類型系統](/articles/typescript 簡介-typescript 筆記 1/#articleHeader6)):
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;
}
同樣,這些重載類型聲明僅作用於編譯時,因此也有類似於 [模式匹配](/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);
}
// 這裡 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.
暫無評論,快來發表你的看法吧