函式_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.