定义:将上次的计算结果缓存起来,当下次调用时,若是遇到相同的参数,就直接返回缓存中的数据。javascript
let add = (a,b) => a+b; let calc = memoize(add); calc(10,20);//30 calc(10,20);//30 缓存
若是要实现以上功能,主要依靠 闭包 、柯里化、高阶函数 html
实现原理:把参数和对应的结果数据存在一个对象中,调用时判断参数对应的数据是否存在,存在就返回对应的结果数据,不然就返回计算结果。java
理论有了,咱们来实现一个缓存函数:编程
let memoize = function (func, content) { let cache = Object.create(null) content = content || this return (...key) => { if (!cache[key]) { cache[key] = func.apply(content, key) } return cache[key] } }
过程分析:api
cache
cache
的中。若是已经存在,直接返回cache
的内容,若是没有存在,使用函数func
对输入参数求值,而后把结果存储在cache
中。在Vue中也有所体现数组
/** * Create a cached version of a pure function. */ function cached (fn) { var cache = Object.create(null); return (function cachedFn (str) { var hit = cache[str]; return hit || (cache[str] = fn(str)) }) } /** * Capitalize a string. */ var capitalize = cached(function (str) { return str.charAt(0).toUpperCase() + str.slice(1) }); ... capitalize(camelizedId)
适用场景:缓存
function currying
把接受多个参数的函数转换成接受一个单一参数的函数闭包
// 非函数柯里化 var add = function (x,y) { return x+y; } add(3,4) //7 // 函数柯里化 var add2 = function (x) { //**返回函数** return function (y) { return x+y; } } add2(3)(4) //7
在上面的例子中,咱们将多维参数的函数拆分,先接受第一个函数,而后返回一个新函数,用于接收后续参数。app
就此,咱们得出一个初步的结论:柯里化后的函数,若是形参个数等于实参个数,返回函数执行结果,否者,返回一个柯里化函数。函数式编程
经过柯里化可实现代码复用,使用函数式编程。
从上面例子中,咱们定义了有两个形参的函数,为了实现柯里化,函数传入第一个形参后返回一个函数用来接收第二个形参。那么若是咱们的定义的形参有三个,那么也就须要嵌套2层,分别处理后两个参数,如
var add3 = function (x) { return function (y) { return function (z) { return x + y + z; } } } add3(1)(3)(5)
若是形参有5个,7个呢?这里咱们使用递归,进行简化。不知有没有看到规律,形参的个数决定了函数的嵌套层数。 即 有n个参数就得嵌套n-1个函数 ,那咱们来改造一番。
// 通用型柯里化 function currying (fn) { // 未柯里化函数所需的参数个数 https://www.cnblogs.com/go4it/p/9678028.html var limit = fn.length; var params = []; // 存储递归过程的全部参数,用于递归出口计算值 return function _curry(...args) { params = params.concat(args); // 收集递归参数 if (limit <= params.length) { let tempParams=params.slice(0,limit) if(limit===params.length){ //参数个数知足时清除已缓存的参数 params=[] } // 返回函数执行结果 return fn.apply(null, params); } else { // 返回一个柯里化函数 return _curry; } }; } function add(x,y,z){ return x + y+z; } // 函数柯里化 var addCurried=currying(add); console.log(`addCurried(1)(2)(3)`,addCurried(1)(2)(3))//6 console.log(`addCurried(3,3,3)`,addCurried(3,3,3))//9 console.log(`addCurried(1,2)(3)`,addCurried(1,2)(3))//6 console.log(`addCurried(3)(4,5)`,addCurried(3)(4,5))//12
咱们看看addCurried(1)(2)(3)
中发生了什么:
`addCurried(1)
,将1
保存在词法环境中,而后递归调用_curry
继续收集后续参数addCurried(1)(2)
,参数2
与第一次的参数1
,合并调用,因未达到形参个数要求,继续递归返回_curry
addCurried(1)(2)(3)
,参数为3
,在接下去的调用中,与1,2
进行合并,传入原函数add
中Array.prototype.slice.call
转换为数组时,效率低。简单描述,就是把一个函数的某些参数先固化,也就是设置默认值,返回一个新的函数,在新函数中继续接收剩余参数,这样调用这个新函数会更简单。
// 乘法 let multi = (x,y) => x * y; // 构造一个对数值乘以2的函数 let double = multi.bind(null,2); console.log(double(3));//6 console.log(double(5));//10
在这个例子中,咱们使用bind
固定了 乘数,返回一个函数。该函数接受一个参数做为 被乘数。--将部分参数固定,只对剩余参数进行计算。
基于以上推导,咱们来实现一个无绑定上下文的偏函数:
/** * 偏函数实现 * @param func 应用函数 * @param argsBound 固定参数 * @return {function(...[*]): *} */ let partial = (func, ...argsBound) => { if (typeof func !== 'function') throw new TypeError( `${typeof func} is not a function`) return function (...args) { // (*) if(func.length-argsBound.length>args.length) throw new Error(`miss arguments`) return func.call(this, ...argsBound.concat(...args)) } } let partialMulti= partial(multi,2) console.log(partialMulti());//Error: miss arguments console.log(partialMulti(3));//6
partial(func[, arg1, arg2...])
调用的结果是一个基于 func
的封装函数,以及:
this
...argsBound
—— 来自偏函数调用传入的参数...args
—— 传入封装函数的参数偏函数与柯里化很类似,下面咱们作个对比:
柯里化:将一个对参数函数转换成多个单参数的函数,也就是将一个n元函数转换为n个一元函数。
偏函数:固定一个函数的一个或多个参数,也就是将一个n元函数转换成一个n-x元函数。
我的理解:偏函数是柯里化的一种特定的应用场景