參考https://github.com/zhongsp/TypeScript
函數(shù)是JavaScript應用程序的基礎。 它幫助你實現(xiàn)抽象層,模擬類,信息隱藏和模塊。 在TypeScript里,雖然已經支持類,命名空間和模塊,但函數(shù)仍然是主要的定義行為的地方。 TypeScript為JavaScript函數(shù)添加了額外的功能,讓我們可以更容易地使用。
一、JS中的函數(shù)
通過下面的例子可以迅速回想起這兩種JavaScript中的函數(shù):
// Named function
function add(x, y) {
return x + y;
}
// Anonymous function
let myAdd = function(x, y) { return x + y; };
在JavaScript里,函數(shù)可以使用函數(shù)體外部的變量。 當函數(shù)這么做時,我們說它‘捕獲’了這些變量。 至于為什么可以這樣做以及其中的利弊超出了本文的范圍,但是深刻理解這個機制對學習JavaScript和TypeScript會很有幫助。
let z = 100;
function addToZ(x, y) {
return x + y + z;
}
二、為函數(shù)定義類型
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x + y; };
我們可以給每個參數(shù)添加類型之后再為函數(shù)本身添加返回值類型。 TypeScript能夠根據(jù)返回語句自動推斷出返回值類型,因此我們通常省略它。
三、參數(shù)
1.可選參數(shù)
JavaScript里,每個參數(shù)都是可選的,可傳可不傳。 沒傳參的時候,它的值就是undefined。
TypeScript里的每個函數(shù)參數(shù)都是必須的。 這不是指不能傳遞null或undefined作為參數(shù),而是說編譯器檢查用戶是否為每個參數(shù)都傳入了值。 編譯器還會假設只有這些參數(shù)會被傳遞進函數(shù)。 簡短地說,傳遞給一個函數(shù)的參數(shù)個數(shù)必須與函數(shù)期望的參數(shù)個數(shù)一致。
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
// error, too few parameters
let result1 = buildName("Bob");
// error, too many parameters
let result2 = buildName("Bob", "Adams", "Sr.");
// ah, just right
let result3 = buildName("Bob", "Adams");
在TypeScript里我們可以在參數(shù)名旁使用?實現(xiàn)可選參數(shù)的功能。 可選參數(shù)必須跟在必須參數(shù)后面。
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
2.參數(shù)默認值
在TypeScript里,我們也可以為參數(shù)提供一個默認值當用戶沒有傳遞這個參數(shù)或傳遞的值是undefined時。 它們叫做有默認初始化值的參數(shù)。
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
3.剩余參數(shù)
必要參數(shù),默認參數(shù)和可選參數(shù)有個共同點:它們表示某一個參數(shù)。 有時,你想同時操作多個參數(shù),或者你并不知道會有多少參數(shù)傳遞進來。 在JavaScript里,你可以使用arguments來訪問所有傳入的參數(shù)。
在TypeScript里,你可以把所有參數(shù)收集到一個變量里:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
剩余參數(shù)會被當做個數(shù)不限的可選參數(shù)。 可以一個都沒有,同樣也可以有任意個。 編譯器創(chuàng)建參數(shù)數(shù)組,名字是你在省略號(...)后面給定的名字,你可以在函數(shù)體內使用這個數(shù)組。
四、this
1.JS中的this
如果你以前沒用過JS,有一件事可能會令你驚訝:JS中的每個函數(shù)都有this變量,而不局限于類中的方法。以不同的方式調用函數(shù),this的值也不同,這極易導致代碼脆弱、難以理解。鑒于此,很多團隊禁止在類方法以外使用this。如果你也想這么做,開啟eslint的no-invalid-this規(guī)則。
以下參考
js紅寶書筆記十一 第十章 函數(shù) 閉包
js es6 => arrow function箭頭函數(shù)
window.color = 'red';
let o = {
color: 'blue'
};
function sayColor() {
console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue'
定義在全局上下文中的函數(shù) sayColor()引用了 this 對象。這個 this 到底引用哪個對象必須到函數(shù)被調用時才能確定。因此這個值在代碼執(zhí)行的過程中可能會變。如果在全局上下文中調用sayColor(),這結果會輸出"red",因為 this 指向 window,而 this.color 相當于 window.color。而在把 sayColor()賦值給 o 之后再調用 o.sayColor(),this 會指向 o,即 this.color 相當于o.color,所以會顯示"blue"。
此時將回調函數(shù)寫成箭頭函數(shù)就可以解決問題。這是因為箭頭函數(shù)中的 this 會保留定義該函數(shù)時的上下文。
window.color = 'red';
let o = {
color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red'
在es5中有很多方法解決這個問題,但是一般比較常用的方法就是,在調用上面聲明個變量,一般叫做self或者vm,然后把這個用在函數(shù)中
let obj = {
name: "asim",
sayLater: function () {
let self = this; // Assign to self
console.log(self);
setTimeout(function () {
console.log(`${self.name}`); // Use self not this
}, 1000);
}
};
但是在es6中,我們有更好的方式解決這個問題,如果我們使用箭頭函數(shù),那箭頭函數(shù)里面的this的值和外面的值是一樣的
let obj = {
name: "asim",
sayLater: function () {
console.log(this); // `this` points to obj
setTimeout(() => {
console.log(this); // `this` points to obj
console.log(`${this.name}`); // `this` points to obj
}, 1000);
}
};
obj.sayLater();
2.Typescript的This
this可以說是Javascript里最難理解的特性之一了,Typescript里的 this 似乎更加復雜了,Typescript里的 this 有三中場景,不同的場景都有不同意思。
- this 參數(shù): 限制調用函數(shù)時的 this 類型
- this 類型: 用于支持鏈式調用,尤其支持 class 繼承的鏈式調用
- ThisType:用于構造復雜的 factory 函數(shù)
3.TS this 類型: 用于支持鏈式調用
參考《TypeScript編程》第5.3節(jié)
實現(xiàn)ES6中 Set數(shù)據(jù)結構的簡化版,有以下兩個操作:
let set = new Set;
set.add(1).add(2).add(3);
set.has(2);//true
set.has(4);//false
這里要支持鏈式調用,大概這樣的:
class Set{
has(value:number):boolean{}
add(value:number):Set{}
}
這樣做是可以的,但是如果想定義Set的子類呢?
class MutableSet extends Set{
delete(value:number):boolean{}
add(value:number):MutableSet{}
}
這就需要又寫一遍add去覆蓋,其實可以在父類Set中返回this就不用這么麻煩了:
class Set{
has(value:number):boolean{}
add(value:number):this{}
}
4.TS中this做為對象方法調用,這一點和JS中的this表現(xiàn)一致
這也是絕大部分 this 的使用場景,當函數(shù)作為對象的 方法調用時,this 指向該對象
const obj = {
name: "yj",
getName() {
return this.name // 可以自動推導為{ name:string, getName():string}類型
},
}
obj.getName() // string類型
這里有個坑就是如果對象定義時對象方法是使用箭頭函數(shù)進行定義,則 this 指向的并不是對象而是全局的 window,Typescript 也自動的幫我推導為 window
const obj2 = {
name: "yj",
getName: () => {
return this.name // check 報錯,這里的this指向的是window
},
}
obj2.getName() // 運行時報錯
5.TS中this在普通函數(shù)中使用
即使是通過非箭頭函數(shù)定義的函數(shù),當將其賦值給變量,并直接通過變量調用時,其運行時 this 執(zhí)行的并非對象本身
const obj = {
name: "yj",
getName() {
return this.name
},
}
const fn1 = obj.getName
fn1() // this指向的是window,運行時報錯
很不幸,上述代碼在編譯期間并未檢查出來,我們可以通過為getName添加this的類型標注解決該問題
interface Obj {
name: string
// 限定getName調用時的this類型
getName(this: Obj): string
}
const obj: Obj = {
name: "yj",
getName() {
return this.name
},
}
obj.getName() // check ok
const fn1 = obj.getName
fn1() // check error
這樣我們就能報保證調用時的 this 的類型安全.
關于這個作用,《TypeScript編程》第4.1.4節(jié)這樣描述,此時this不是常規(guī)的參數(shù),而是保留字,是函數(shù)簽名的一部分。
6.TS中this在構造函數(shù)中使用
class People {
name: string
constructor(name: string) {
this.name = name // check ok
}
getName() {
return this.name
}
}
const people = new People("yj") // check ok
這里面還是有坑的,具體可以參考原文:詳解Typescript里的This
7.call 和 apply 調用
call 和 apply 調用沒有什么本質區(qū)別,主要區(qū)別就是 arguments 的傳遞方式,不分別討論。和普通的函數(shù)調用相比,call 調用可以動態(tài)的改變傳入的 this, 幸運的是 Typescript 借助 this 參數(shù)也支持對 call 調用的類型檢查
interface People {
name: string
}
const obj1 = {
name: "yj",
getName(this: People) {
return this.name
},
}
const obj2 = {
name: "zrj",
}
const obj3 = {
name2: "zrj",
}
obj1.getName.call(obj2)
obj1.getName.call(obj3) // check error
五、函數(shù)重載
JavaScript本身是個動態(tài)語言。 JavaScript里函數(shù)根據(jù)傳入不同的參數(shù)而返回不同類型的數(shù)據(jù)是很常見的。
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
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)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
pickCard方法根據(jù)傳入?yún)?shù)的不同會返回兩種不同的類型。 如果傳入的是代表紙牌的對象,函數(shù)作用是從中抓一張牌。 如果用戶想抓牌,我們告訴他抓到了什么牌。 但是這怎么在類型系統(tǒng)里表示呢。
方法是為同一個函數(shù)提供多個函數(shù)類型定義來進行函數(shù)重載。 編譯器會根據(jù)這個列表去處理函數(shù)的調用。 下面我們來重載pickCard函數(shù)。
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 {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
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)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
這樣改變后,重載的pickCard函數(shù)在調用的時候會進行正確的類型檢查。
為了讓編譯器能夠選擇正確的檢查類型,它與JavaScript里的處理流程相似。 它查找重載列表,嘗試使用第一個重載定義。 如果匹配的話就使用這個。 因此,在定義重載的時候,一定要把最精確的定義放在最前面。
注意,function pickCard(x): any并不是重載列表的一部分,因此這里只有兩個重載:一個是接收對象另一個接收數(shù)字。 以其它參數(shù)調用pickCard會產生錯誤。
1.重載簽名
參考
TypeScript中函數(shù)重載寫法,你在第幾層!
TypeScript中的函數(shù)重載讓我們定義以多種方式調用的函數(shù)。使用函數(shù)重載需要定義重載簽名:一組帶有參數(shù)和返回類型的函數(shù),但沒有主體。這些簽名表明應該如何調用該函數(shù)。
// 重載簽名
function greet(person: string): string;
function greet(persons: string[]): string[];
// 實現(xiàn)簽名
function greet(person: unknown): unknown {
if (typeof person === 'string') {
return `Hello, ${person}!`;
} else if (Array.isArray(person)) {
return person.map(name => `Hello, ${name}!`);
}
throw new Error('Unable to greet');
}
2.參考如何看待Typescript中的重載(Overload)? - 賀師俊的回答 - 知乎
第一,常見的靜態(tài)類型語言中的overload是發(fā)生在編譯時的,編譯器可以清楚的將每一處同名函數(shù)調用對應到你寫的不同的函數(shù)實現(xiàn)。也就是,同名只是一個(讓程序員看到的)假象。如你寫了fun(x),有兩個實現(xiàn)fun(x: string)、fun(x: int),真正編譯后的程序里實際會有兩個函數(shù),假設記做fun_string和fun_int,而每個fun(x)調用會被自動替換成fun_string(x)或fun_int(x)。有沒有可能編譯器無法確定替換成哪一個?當然有可能,這個時候編譯器就報錯了嘛,意思是你代碼寫錯啦!
第二,JavaScript是動態(tài)類型,所以是沒有上面這種意義上的overload的。但JS程序員可以在運行時判斷類型,也就是 function fun(x) { if (typeof x === 'string') ... else/* assume x is int */ ... }。TypeScript 的『overload』只是允許給這樣的函數(shù)標注多個類型。某輪說這是『繞過編譯器類型檢查』,是有問題的。這不是繞過,把函數(shù)參數(shù)標記為 (x: any) 才叫『繞過』。不過因為函數(shù)的具體實現(xiàn)只有一個,代碼本身會比上面那種overload要麻煩一些,比如說為了檢測類型偶爾你需要自己實現(xiàn)一些 type guard。至于說[下標函數(shù)]也不能自己寫,這個很傻逼』,我估計某輪指的是 operator overload,然而很多語言都不允許(比如 java)。所以單單罵 TS/JS 有點扯。
第三,TS理論上當然是可以實現(xiàn)傳統(tǒng)的 overload 的,比如直接生成兩個函數(shù),fun1、fun2。問題是從TS與JS的互操作性上來說,這事情就比較麻煩,比如一個js項目用了ts的庫,我不能直接寫fun,而得寫fun1、fun2。本來 overload 就是希望給程序員提供便利,但現(xiàn)在就并沒有什么卵用。其實像java之類有『真』重載的語言編譯到js,或直接和js互操作,都有類似的問題。早在二十年前rhino里就有這問題——你在js里要指定到底調用的是哪一個java的重載方法是非常煩人的。特別是構造器,一般函數(shù)你說編譯成fun_string、fun_int也就算了,但構造器呢?相當棘手。