函数
函数的定义
定义一个最简单的加法函数:
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) + d
for(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) // 首先打印 log
return 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 - b
function processArray(a: number[]) {
let compCount = 0
const 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 = 2
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(filterArray(a, (v) => isGoodNumber(GOOD_FATOR, v)))