# 高阶函数

该函数接收一个函数作为参数,返回另外一个函数。

  • filterLowerThan10这个函数接收一个数组作为参数,会挑选出数组中数值小于10的项,所有符合条件的项会构成一个新数组并返回
const filterLowerThan10 = array => {
    let result = []
    for(let i=0;i<array.length;i++) {
        let currentValue = array[i]
        if(currentValue < 10) result.push(currentValue)
    }
    return result
}
1
2
3
4
5
6
7
8

这个例子代码不够优雅,是很明显的面向过程编程,都存在遍历逻辑和for循环,可以利用filter的函数式思想进行改写。

# 柯里化

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2);
// 3
addTen(2);
// 12
add(1)(2);
// 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这里定义了一个 add 函数,它接受一个参数并返回一个新的函数。调用 add 之后,返回的函数就通过闭包的方式记住了 add 的第一个参数。所以说 bind 本身也是闭包的一种使用场景。

那肯定不能连续多次return函数的,所以可以这么实现柯里化函数:

function curryIt(fn) {
    var args = [];
    var result = function(arg){
        args.push(arg);
        if(args.length < fn.length){ // fn.length获取形参个数
            return result;
        } else {
            return fn.apply(this,args);
        }
    }
    return result;
} 

var fn = function(a,b,c) {
    console.log(a+b+c) // 6
    return a+b+c;
}
curryIt(fn)(1)(2)(3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

另一种版本:

function curry(fn, args) {
    var length = fn.length;
    var args = args || [];
    return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

侯策一书的柯里化实现版本

add(1)(2) == 3 // true
1

每次执行add函数后一定要保证返回一个函数,以供后续继续调用,且返回的这个函数还要返回自身,以支持连续调用。

const add = arg1 => {
    const fn = arg2 => {
        return fn
    }
    fn.toString = function() {

    }
    return fn
}
1
2
3
4
5
6
7
8
9

为了进行求和操作,需要在add函数内部维护一个闭包变量argsargs是一个数组,用于记录每次调用时传入的参数,并在toString方法体中对参数进行求和,在fn方法体中将当前参数添加到数组中args中。改写toString只是为了在==情况下进行类型转换,如果是想要直接输出结果就用上面的方法

const add = arg1 => {
    let args = [arg1]
    const fn = arg2 => {
        args.push(arg2)
        return fn
    }
    fn.toString = function(){
        return args.reduce((prev,item) => prev+item, 0)
    }
    return fn
}
1
2
3
4
5
6
7
8
9
10
11

如果为了支持add(1)(2,3)(4)这种参数形式,可以改写为:

const add = (...arg1) => {
    let args = [...arg1]
    const fn = (...arg2) => {
        args = [...args,...arg2]
        return fn
    }
    fn.toString = function() {
        return args.reduce((prev,item) => prev+item, 0)
    }
    return fn
}

function add() {
  let argsArr = Array.from(arguments)
  let fn = (...args) => {
    argsArr = argsArr.concat(args)
    return fn
  }
  fn.toString = () => {
    return argsArr.reduce((prev,curr) => {
      return prev + curr
    },0)
  }
  return fn
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 通用柯里化

const curry = (fn,length) => {
    length = length || fn.length
    return function(...args) {
        if(args.length < length) {
            return cury(fn.bind(this,...args),length - args.length)
        } else {
            return fn.call(this,...args)
        }
    }
}

// 不适用bind
const curry = fn => {
    return tempFn = (...arg1) => {
        if(arg1.length >= fn.length) {
            return fn(...arg1)
        } else {
            return (...arg2) => tempFn(...arg1,...arg2)
        }
    }
}

// 简化
const curry = fn => 
    judge = (...arg1) => 
        arg1.length >= fn.length
            ? fn(...arg1)
            : (...arg2) => judge(...arg1,...arg2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

TIP

实现原理:先用闭包把传入的参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数。步骤:

  • 先逐步接收参数,并进行存储,以供后续使用
  • 先不进行函数计算,延后执行
  • 在符合条件时,根据已存储的参数进行统一计算

# 反柯里化

反柯里化旨在扩大函数的适用性,使本来作为特定对象所拥有的功能函数可以被任意对象所使用 unCurry方法的参数是一个希望被其他对象所调用的方法,暂且被称为fn,unCurry方法执行后会返回一个新的函数,该函数的第一个参数是预期要执行方法的对象,后面的参数是执行这个方法时需要传递的参数。

function unCurry(fn) {
    return function() {
        var obj = [].shift.call(arguments)
        return fn.apply(obj,arguments)
    }
}
1
2
3
4
5
6

# 利用高阶函数进行缓存

函数返回值缓存。实现一个memorize函数,对函数返回值进行缓存,以减少函数实际运行的开销。

const memorize = (fn) => {
  let cache = {}
  return function(...args) {
    let cacheKey = args.join('_')
    console.log(cacheKey)
    if(cacheKey in cache) {
      console.log('命中缓存')
      return cache[cacheKey]
    } else {
      return cache[cacheKey] = fn.apply(this || {},args)
    }
  }
}

function test(name,age) {
  console.log(name,age)
}

let mem = memorize(test)
mem(1,2,3)
mem(1,2,3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 实现compose

compose的函数作用就是组合函数,将函数串联起来执行。将多个函数组合起来,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数执行,就会像多米诺骨牌一样。

  • compose 的参数是函数数组,返回的也是一个函数
  • compose 的参数是任意长度的,所有的参数都是函数,执行方向是自右向左的,因此初始函数一定放到参数的最右面
  • compose 执行后返回的函数可以接收参数,这个参数将作为初始函数的参数,所以初始函数的参数是多元的,初始函数的返回结果将作为下一个函数的参数,以此类推。因此除了初始函数之外,其他函数的接收值是一元的。
const greeting = (name) => `hello ${name}`
const toUpper = (str) => str.toUpperCase()
const add = (str2) => `${str2}哈哈哈害`
const fn = compose(toUpper,greeting)
console.log(fn('linjiaheng'))
const newFn = compose(fn,add)
console.log(newFn('linjiaheng前端工程师'))

// 递归
/**
 * 这里关键用到了闭包,使用闭包变量存储结果result和函数数组长度,以及遍历索引,并利用递归思想进行结果的累加计算。
 * 整体符合正常的面向过程的思维。
 * 
 */
function compose(...func) {
  let len = func.length,
    count = len -1,
    result = null
  return function f1(...args) { 
    result = func[count].apply(this,args)
    if(count <= 0) {
      count = len-1
      return result
    } 
    count--
    return f1.call(null,result)
  }
}

// 迭代
function compose(...func) {
  let isFirst = true
  return function(...args) {
    return func.reduceRight((result,fn) => {
      if(!isFirst) {
        return fn(result)
      }
      isFirst = false
      return fn(...result)
    },args)
  }
}

// reduce
function reduceFunc (prev, curr) {
  return (...args) => {
    return curr.call(this, prev.apply(this, args))
  }
}

function compose (...args) {
  return args.reverse().reduce(reduceFunc, args.shift())
}

// Promise版本
const compose = (...args) => {
  let init = args.pop()
  return (...arg) =>
  args.reverse().reduce((sequence,func) => 
  sequence.then(result => func.call(null,result))
  , Promise.resolve(init.apply(null,arg)))
}

// lodash版本
var compose = function(funcs) {
  var length = funcs.length
  var index = length
  while(index--) {
    if(typeof func[index] !== 'function') {
      throw new TypeError('Expected a function')
    }
  }
  return function(...args) {
    var index = 0
    var result = length ? func.reverse()[index].apply(this,args) : args[0]
    while(++index < length) {
      result = funcs[index].call(this,result)
    }
    return result
  }
}

// redux版本
function compose(...funcs) {
  if(funcs.length === 0) {
    return arg => arg
  }
  if(funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a,b) => (...args) => a(b(...args)))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92