函數
函數的定義
定義一個最簡單的加法函數:
function add(a: number, b: number): number {return a + b
}
(可以看到 JavaScript/TypeScript 的語法與 Golang 也非常的相似)
調用該函數:
console.log(add(2, 3))
// out
[LOG]: 5
可選參數、默認參數和可變參數列表
在函數定義時,假定我們有一個可選的參數,可以在形參列表該參數后加上?
。注意可選參數和默認參數一樣都應該放在參數列表的后面:
function add(a: number, b: number, c?: number, d: number = 0): number {return a + b + (c || 0) + d
}console.log(add(2, 3))
console.log(add(2, 3, 9))
console.log(add(2, 3, 9, 15))
// out
[LOG]: 5
[LOG]: 14
[LOG]: 29
還可以加入一個可變參數列表:
function add(a: number, b: number, c?: number, d: number = 0,...e: number[]): number {let sum = a + b + (c || 0) + dfor(let i = 0; i < e.length; i ++) {sum += e[i]}return sum
}console.log(add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
// out
[LOG]: 55
函數的重載
TypeScript 支持函數的重載(Golang 不允許函數的重載),但是不建議使用。
對象類型參數
假設我們現在有下述的函數定義:
function sendRequest(url: string, method: 'GET' | 'POST' | 'PUT',header: object,data: string,requireAuth: boolean,retry: boolean,retryTimeOut: number) {// ... ... ...
}
函數掉調用者在使用這個函數的時候,可能會給出非常冗長的參數列表,且我們無法從參數列表得知這個函數在做什么。
TypeScript 的一個解決辦法是使用對象類型參數:
function sendRequest(params: {url: string, method: 'GET' | 'POST' | 'PUT',header: object,data: string,requireAuth: boolean,retry: boolean,retryTimeOut?: number,}) {// ... ... ...
}
參數直接是一個對象。函數調用時:
sendRequest({url: 'https://www.test.com',method: 'GET',header: {contentType: '... ... ...'},data: '{}',requireAuth: true,retry: true,retryTimeOut: 3000
})
這種做法使得函數調用者非常的方便。
通過函數為對象定義方法
將函數定義在對象內部,即可完成對象方法的定義:
const emp1 = {name: 'John',salary: 8000,performance : 3.5,bonus: undefined as (number | undefined),updataBonus() {if(!emp1.bonus) {emp1.bonus = emp1.salary * emp1.performance}},
}emp1.updataBonus()
console.log(emp1)// out
[LOG]: {"name": "John","salary": 8000,"performance": 3.5,"bonus": 28000
}
觀察對象方法的定義:
updataBonus() {if(!emp1.bonus) {emp1.bonus = emp1.salary * emp1.performance}
}
我們看到,必須通過 emp1 才能訪問 bonus,但如果我們使用 emp2、emp3 來保存對象該怎么辦呢?可以通過使用保留的關鍵字 this 來解決上述問題,修改后的方法如下:
updataBonus() {if(!this.bonus) {this.bonus = this.salary * this.performance}
}
函數式編程
函數式編程是 TypeScript/JavaScript 非常大的一個亮點,這個特性非常適配于前端開發。TypeScript/JavaScript 的 Promise 也是基于函數式編程的。
使用函數式編程輔助數組的排序
之前在對數組的學習過程當中我們提到過,直接對 number 類型的數組使用 sort 方法之后,數組將按照字典順序排序,而不是按照數字大小的順序排序。現在我們希望借助函數實現按照 number 大小進行排序:
function compareNumber(a: number, b: number) {// a < b -> 返回負數// a === b -> 返回 0// a > b -> 返回正數return a - b
}let a = [5, 2, 1, 6, 8, 10, 5, 25, 16, 23, 11]
a.sort(compareNumber)
console.log(a)
👆將函數名稱傳遞給函數,就是函數式編程。(看起來和 C++ 當中的函數指針非常的像,但函數式編程比函數指針復雜很多,唯一和函數指針相似的地方就是在上述例子當中)
在函數式編程中,函數是一等公民
函數作為一等公民時:
- 變量類型可以是函數;
- 值(literal)可以是函數;
- 對象的字段可以是函數;
- 函數的參數也可以是函數;
- 函數的返回值可以是函數。
變量類型可以是函數
上述的 compareNumber 函數的另一種定義形式如下:
const compareNumber = function(a: number, b: number) {return a - b
}
此時,compareNumber 是一個對象,它的類型是函數。
值(literal)可以是函數
上述 compareNumber 可以是一個變量,并被賦予其它的值(其它函數):
let compareNumber = function(a: number, b: number) {return a - b
}
compareNumber = function(a: number, b: number) {return b - a
} // 實現降序排序
對象的字段也可以是函數
一個例子如下:
const emp1 = {name: 'John',salary: 8000,increaseSalary: function(p: number) { // 此處不能使用箭頭函數this.salary *= p // 箭頭函數和 this 之間有坑}
}
函數的參數可以是函數
比如 sort 的參數是 compareNumber。
函數的返回值可以是參數
一個例子如下:
function createComparer(greater: boolean = false) {return greater ? (a: number, b: number) => b - a : (a: number, b: number) => a - b
}let a = [5, 2, 1, 6, 8, 10, 5, 25, 16, 23, 11]
a.sort(createComparer())
console.log(a)
lambda 表達式
上述 compareNumber 函數更簡單的寫法如下:
let compareNumber = (a: number, b: number) => a - b
它是 lambda 表達式,在 TypeScript / JavaScript 中也被稱為箭頭函數。
一個快速實現排序的方法是:
a.sort((a: number, b: number) => a - b)
=>
后面可以像函數體一樣使用{ ... }
包裹,但此時必須有返回值。
高階函數
高階函數指的就是返回值是函數的函數,這個概念類似于函數的疊加與嵌套。一個例子如下:
function loggingComparer(comp: (a: number, b: number) => number) {return (a: number, b: number) => { // 對作為參數的函數進行包裝console.log('comparing', a, b) // 首先打印 logreturn comp(a, b) // 再調用作為參數傳入的函數} // 看起來很像 Python 的 decorator
}
函數的閉包
在上述高階函數 loggingComparer 的基礎上,我們希望知道排序函數總共進行了多少次比較操作。可以通過函數的閉包(而不是設置全局變量)來實現上述功能。(使用全局變量的缺點在于全局變量或對象的狀態字段需要維護,此外,打印同樣改變了前端 UI 元素的狀態,它同樣也是一個副作用,我們應該盡可能地減少副作用,以提高用戶體驗)
一個函數閉包的例子如下,在下述代碼片段中,函數 processArray 當中的局部函數 logger 和變量 compCount 是一個閉包,compCount 是它所攜帶的自由變量:
function loggingComparer(logger: (a: number, b: number) => void, comp: (a: number, b: number) => number) {return (a: number, b: number) => { logger(a, b) return comp(a, b) }
}function createComparer(params: {greater: boolean}) {return params.greater ? (a: number, b: number) => b - a : (a: number, b: number) => a - b
}let compareNumber = (a: number, b: number) => a - bfunction processArray(a: number[]) {let compCount = 0const logger = (a: number, b: number) => {console.log('comparing', a, b)compCount ++ }const comp = createComparer({greater: false})a.sort(loggingComparer(logger, comp))return compCount
}let a = [5, 2, 1, 6, 8, 10, 5, 25, 16, 23, 11]const compCount = processArray(a)
console.log(a)
console.log('Compare Count: ', compCount)
在上述例子中,隨著 processArray 函數調用的結束,logger 函數也隨之結束,compCount 作為返回值返回。但是如果 processArray 函數調用結束時,其內部的閉包由于某種原因尚未停止運行(比如一個線程),那么其所攜帶的自由變量的生命周期將會超越該函數的局部作用域。
部分應用函數
基于閉包可以實現部分應用函數。
一個部分應用的例子如下:
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(a.filter((v) => v % 2 == 0))
// out
[LOG]: [2, 4, 6, 8]
一個等價的實現如下:
function isGoodNumber(goodFactor: number, v: number) {return v % goodFactor === 0
}function filterArray(a: number[], f: (v: number) => boolean) {return a.filter(f)
}const GOOD_FATOR = 2const a = [1, 2, 3, 4, 5, 6, 7, 8, 9]console.log(filterArray(a, (v) => isGoodNumber(GOOD_FATOR, v)))