函数式编程
函数作为“一等公民”
- 变量类型可以是函数
以下代码推荐写成lambda表达式的形式(在JavaScript/TypeScript中也被称为箭头函数)
const compareNumber = (a: number, b: number) => a-b
箭头函数:用箭头指向函数的结果,也可以用箭头指向{函数体}
// lambda 表达式, JavaScript/TypeScript:箭头函数
//const compareNumber = (a: number, b: number) => a-b
const compareNumber = function (a:number,b:number) {
return b-a
}
let a = [5,2,1,6,8,10,5,25,16,23,11]
a.sort(compareNumber)
- 值(literal)可以是函数
let a = [5,2,1,6,8,10,5,25,16,23,11]
a.sort((a: number, b: number) => {
return a-b
})
console.log(a)
- 对象的字段可以是函数
const emp1 = {
name: 'john',
salary: 8000,
increaseSalary: function (p: number) {
this.salary *= p
}
}
- 函数的参数可以是函数
function compareNumber(a:number,b:number) {
return a-b
}
let a = [5,2,1,6,8,10,5,25,16,23,11]
a.sort(compareNumber)
- 函数的返回值可以是函数
这里的createComparer函数中具有boolean类型的参数,如果直接传入false的话不知道false具体有什么作用,所以这里使用了对象类型参数在调用函数时可以很清晰的看出false的变量为smallerFirst
function createComparer(p: {
smallerFirst: boolean}){
if (p.smallerFirst) {
return (a: number, b: number) => a-b
} else {
return (a: number, b: number) => b-a
}
}
let a = [5,2,1,6,8,10,5,25,16,23,11]
a.sort(createComparer({
smallerFirst: false}))
高阶函数
函数的参数可以是函数
函数的返回值可以是函数
以上两种都属于高阶函数
loggingComparer():函数的参数和返回值都是函数
function loggingComparer(comp: (a: number, b: number) => number){
return (a: number, b: number) => {
console.log('comparing', a, b)
return comp(a, b)
}
}
function createComparer(p: {
smallerFirst: boolean}){
if (p.smallerFirst) {
return (a: number, b: number) => a-b
} else {
return (a: number, b: number) => b-a
}
}
let a = [5,2,1,6,8,10,5,25,16,23,11]
const comp = createComparer({
smallerFirst: true})
a.sort(loggingComparer(comp))
闭包
使局部变量的生命周期延长
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(p: {
smallerFirst: boolean}){
if (p.smallerFirst) {
return (a: number, b: number) => a-b
} else {
return (a: number, b: number) => b-a
}
}
function processArray(a: number[] ){
let compCount = 0
// logger:闭包
const logger = (a: number, b: number) => {
console.log('comparing', a, b)
compCount++ //自由变量
}
const comp = createComparer({
smallerFirst: true})
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,compCount)
部分应用函数
下例中isGoodNumber有两个参数,我们希望先用一个已知的goodFactor,使用这个goodFactor去判断我们收到的每个v
即我们现在只有一个参数goodFactor,我们想先将goodFactor传入函数,这样isGoodNumber就只有一个参数,此时只有一个参数的isGoodNumber就可以作为filterArray的参数f传入filterArray
解决方法:使用闭包将goodFactor放入函数体中
方法一(推荐使用):
function isGoodNumber(goodFactor: number, v: number){
return v % goodFactor === 0
}
function filterArray(a: number[], f: (v: number) => boolean) {
return a.filter(f)
}
const GOOD_FACTOR = 2
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(filterArray(a, (v) => isGoodNumber(GOOD_FACTOR, v)))
方法二(加深印象):
function isGoodNumber(goodFactor: number, v: number){
return v % goodFactor === 0
}
function filterArray(a: number[], f: (v: number) => boolean) {
return a.filter(f)
}
function partiallyApply(
f: (a: number, b: number) => boolean,
a: number) {
return (b: number) => f(a, b)
}
const GOOD_FACTOR = 2
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(filterArray(a, partiallyApply(isGoodNumber, GOOD_FACTOR)))
Promise
回调函数会引起 “callback hell” 问题,如下例只能通过嵌套的方法计算2+3+4,会导致多层嵌套
function add (a: number, b: number, callback: (res: number) => void): void{
setTimeout(()=>{
callback(a+b)
},2000)
}
add(2,3,res => {
console.log('2+3',res)
add(res,4,res2=>{
console.log('2+3+4',res2)
})
})
改写为promise,代码更加清晰
function add (a: number, b: number): Promise<number>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(a+b)
},2000)
})
}
add(2,3).then(res => {
console.log('2+3',res)
return add(res,4)
}).then(res => {
console.log('2+3+4',res)
return add(res,6)
}).then(res =>{
console.log('2+3+4+6',res)
})
同时等待多个Promise
function add (a: number, b: number): Promise<number>{
return new Promise((resolve,reject)=>{
if (b % 17 === 0){
reject(`bad number: ${
b}`)
}
setTimeout(()=>{
resolve(a+b)
},2000)
})
}
function mul(a: number,b: number): Promise<number>{
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(a*b)
}, 3000)
})
}
// (2+3)*(4+5)
Promise.all([add(2,3), add(4,5)]).then(res => {
const [a,b] = res
return mul(a,b)
}).then(res => {
console.log(res)
})
接口
- 作用:描述一个类型
interface Employee {
name: string
salary: number
bonus?: number
}
const emp1: Employee = {
name: 'john',
salary: 8000,
}
const emp2: Employee = {
name: '张三',
salary: 10000,
bonus: 20000,
}
- 可选字段串联
e.name?.first?.startsWith('AAA')
如果e.name为undefined则整个表达式都为undefined
interface Employee {
name?: {
first?: string
last: string
}
salary: number
bonus?: number
}
function hasBadName(e: Employee){
// 如果e.name为undefined则整个表达式都为undefined
return e.name?.first?.startsWith('AAA')
}
- 非空断言
e.name!.first!.startsWith('AAA')
断言e.name e.name.first不为undefined,此时编译器不会报错,若其值为undefined则后果自负(程序运行出错)
function hasBadName(e: Employee){
// ! 程序不会报错,确保name/first一定有值,如果没有值后果自负
return e.name!.first!.startsWith('AAA')
}
- 接口扩展
extends:将已有接口拼成一个更大的接口
interface Employee extends HasName{
salary: number
bonus?: number
}
interface HasName {
name?: {
first?: string
last: string
}
}
- 接口的并集
e: WxButton|WxImage
e只能调用他们共有的属性visible
interface WxButton {
visible: boolean
enabled: boolean
onClick(): void
}
interface WxImage {
visible: boolean
src: string
width: number
height: number
}
function processElement(e: WxButton|WxImage) {
e.visible
}
- 类型断言
e as WxButton
,得到的类型为WxButton
function processElement(e: WxButton|WxImage) {
if ((e as any).onClick) {
const btn = e as WxButton
btn.onClick()
} else {
const img = e as WxImage
console.log(img.src)
}
}
- 类型判断
(e as WxButton).onClick !== undefined
:要想调用其他属性需要首先判断e的类型,这里不能使用typeof判断,因为在js中看不到自己定义的接口,需要通过判断e是否具有某个属性来判断它的类型。
返回值为e is WxButton,编译器可以判断传入的e为WxButton
function processElement(e: WxButton|WxImage) {
if (isButton(e)) {
e.onClick()
} else {
console.log(e.src)
}
}
function isButton(e: WxButton|WxImage): e is WxButton {
return (e as WxButton).onClick !== undefined
}
类
- 类在定义时必须初始化
可以通过构造函数/赋初值的方式
不加private默认为public
class Employee {
name: string
salary: number
private bonus: number = 0
constructor(name: string, salary: number){
this.name = name
this.salary = salary
}
}
- 可以在构造函数的参数前添加public/private,相当于定义属性
可选参数不用初始化
class Employee {
private bonus?: number
constructor(public name: string, public salary: number){
this.name = name
this.salary = salary
}
updateBonus() {
if (!this.bonus){
this.bonus = 20000
}
}
}
- getter/setter
调用时看不出是一个方法,相当于一个属性进行调用
class Employee {
private allocatedBonus?: number
constructor(public name: string,public salary: number){
this.name = name
this.salary = salary
}
// getter/setter
set bonus(v: number){
this.allocatedBonus = v
}
get bonus(){
return this.allocatedBonus || 0
}
}
const emp1 = new Employee('john',8000)
emp1.bonus = 20000
console.log(emp1.bonus) // 20000
console.log(emp1)
// Employee: {
// "name": "john",
// "salary": 8000,
// "allocatedBonus": 20000
// }
- 类的继承
extends
通过super调用基类的构造函数
class Employee {
private allocatedBonus?: number
constructor(public name: string,public salary: number){
this.name = name
this.salary = salary
}
// getter/setter
set bonus(v: number){
this.allocatedBonus = v
}
get bonus(){
return this.allocatedBonus || 0
}
}
class Manager extends Employee {
private reporters: Employee[]
constructor(name: string, salary: number) {
super(name, salary)
this.reporters = []
}
addReporter(e: Employee){
this.reporters.push(e)
}
}
- 类实现接口
隐式实现:不用implements实现,在定义类的时候不会检查属性,在将类赋值给接口时,会挨个比较属性,如果属性不全则会报错
显示实现:使用implements,类在定义的时候需要定义接口的全部属性
interface Employee {
name: string
salary: number
bonus?: number
}
class EmplImpl implements Employee{
private allocatedBonus?: number
constructor(public name: string,public salary: number){
this.name = name
this.salary = salary
}
// getter/setter
set bonus(v: number){
this.allocatedBonus = v
}
get bonus(){
return this.allocatedBonus || 0
}
}
class Manager extends EmplImpl {
private reporters: EmplImpl[] = []
addReporter(e: EmplImpl){
this.reporters.push(e)
}
}
const empImpl = new EmplImpl('john', 8000)
const emp1: Employee = empImpl
- 定义方法选择 显示定义
定义者=实现者(不推荐)
在使用的时候会有很多多余的方法
// 定义接口
interface Service {
login(): void
getTrips(): string
getLic(): string
startTrip(): void
updateLic(lic: string): void
}
// 实现接口
class RPCService implements Service {
login(): void {
throw new Error("Method not implemented.")
}
getTrips(): string {
throw new Error("Method not implemented.")
}
getLic(): string {
throw new Error("Method not implemented.")
}
startTrip(): void {
throw new Error("Method not implemented.")
}
updateLic(lic: string): void {
throw new Error("Method not implemented.")
}
}
const page = {
service: new RPCService() as Service,
onLoginButtonClicked() {
//使用接口
this.service.login()
}
}
- 定义方法选择 隐示定义
定义者=使用者
由使用的人根据自己的需要定义接口,每个接口都很小比较好维护,实现解耦
只能使用隐式实现,显示实现的话在实现时会implements很多不同的接口,每个使用者都有一个接口
// 实现接口
class RPCService {
login(): void {
throw new Error("Method not implemented.")
}
getTrips(): string {
throw new Error("Method not implemented.")
}
getLic(): string {
throw new Error("Method not implemented.")
}
startTrip(): void {
throw new Error("Method not implemented.")
}
updateLic(lic: string): void {
throw new Error("Method not implemented.")
}
}
// 定义接口
interface LoginService {
login(): void
}
const page = {
service: new RPCService() as LoginService,
onLoginButtonClicked() {
//使用接口
this.service.login()
}
}
泛型
用来约束参数类型
const a: number[] = []
等价于const a: Array<number> = []
- 定义
在定义类型时需要表明泛型类型
在调用函数时可以不必表明类型,编译器可以根据参数自己判断类型
class MyArray<T> {
date: T[] = []
add(t: T) {
this.date.push(t)
}
map<U>(f: (v: T) => U): U[] {
return this.date.map(f)
}
print() {
console.log(this.date)
}
}
const a = new MyArray<number>()
a.add(1)
a.add(2000)
a.add(30000)
//(method) MyArray<number>.map<string>(f: (v: number) => string): string[]
console.log(a.map(v => v.toExponential()))
- 使用接口约束泛型类型
T 可以 . 出内容
interface HasWeight {
weight: number
}
class MyArray<T extends HasWeight> {
date: T[] = []
add(t: T) {
this.date.push(t)
}
map<U>(f: (v: T) => U): U[] {
return this.date.map(f)
}
print() {
console.log(this.date)
}
sortByWeight() {
this.date.sort((a, b) => a.weight - b.weight)
}
}
class WeightedNumber {
constructor(public weight: number) {
}
}
const a = new MyArray<WeightedNumber>()
a.add(new WeightedNumber(10000))
a.add(new WeightedNumber(200))
a.add(new WeightedNumber(30000))
a.sortByWeight()
console.log(a)