前言
柯里化是函数式编程中不可或缺的一环,这个函数使得接受多参数的函数可以通过不影响结果的方式使其成为接受单参数的函数及其序列,根据这个特点可以合成出很多类似功能的函数,甚至是以自定义的顺序运行代码,达到复用代码的目的
lodash 中的 curry
函数因为处理了很多边界情况,这里有些我也不是很清楚。因此这次我只会分析基础的 curry
相关的代码,其余的部分不会详细说明,有一些不足欢迎在评论里补充
思路分析
1. 流程图
2. 简易的 curry
函数
这里贴一个自己实现的简易的 curry
函数,以便理清思路。可以看到柯里化最主要的部分就是对函数进行包裹,在这个包裹内把参数存储起来,等到参数的数量足够再执行
function curry(func, ...args1) {
return function wrapper (...args2) {
if (args1.length + args2.length >= func.length) {
return func.apply(null, [...args1, ...args2])
}
return curry(func, ...args1, ...args2)
}
}
源码分析
1. curry
1. 传入参数
func
函数arity
传入函数的参数guard
可以让curry
成为可迭代的函数,用于_.map
类似的函数
2. 源码分析
curry
上的 placeholder
传入到 result
上,让 result
也可以使用 placeholder
。这里的 placehoder
是 lodash 在外部手动设置的,可以通过传入 lodash 本身的变量,即 _
作为占位符
function curry(func, arity, guard) {
arity = guard ? undefined : arity;
var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
result.placeholder = curry.placeholder;
return result;
}
//...
arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
lodash[methodName].placeholder = lodash;
});
//...
2. createWrap
1. 传入参数
func
传入的被柯里化的函数bitmask
标志位thisArg
传递this
指向partials
已经包含的参数holders
占位符argPos
参数的位置,re-arg
函数会用到ary
参数接收的数量,ary
函数会用到arity
后传入的参数
2. 源码分析
function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
if (!isBindKey && typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
var length = partials ? partials.length : 0;
if (!length) {
bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
partials = holders = undefined;
}
ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
arity = arity === undefined ? arity : toInteger(arity);
length -= holders ? holders.length : 0;
if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
var partialsRight = partials,
holdersRight = holders;
partials = holders = undefined;
}
var data = isBindKey ? undefined : getData(func);
var newData = [
func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
argPos, ary, arity
];
if (data) {
mergeData(newData, data);
}
func = newData[0];
bitmask = newData[1];
thisArg = newData[2];
partials = newData[3];
holders = newData[4];
arity = newData[9] = newData[9] === undefined
? (isBindKey ? 0 : func.length)
: nativeMax(newData[9] - length, 0);
if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
}
if (!bitmask || bitmask == WRAP_BIND_FLAG) {
var result = createBind(func, bitmask, thisArg);
} else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
result = createCurry(func, bitmask, arity);
} else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
result = createPartial(func, bitmask, thisArg, partials);
} else {
result = createHybrid.apply(undefined, newData);
}
var setter = data ? baseSetData : setData;
return setWrapToString(setter(result, newData), func, bitmask);
}
检测 createWrap
是否是用于 bind
绑定函数
var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
if (!isBindKey && typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
partials
为 undefined
,length
置为 0
var length = partials ? partials.length : 0;
如果 length
为 0
,把 partial
和 partialRight
功能也关闭,同时 holders
和 partials
置为 0
,ary
、arity
和传入时保持一致
if (!length) {
bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
partials = holders = undefined;
}
ary
不变,arity
不变,length
置为 0
(根据 holders
占位符的数量决定)
ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
arity = arity === undefined ? arity : toInteger(arity);
length -= holders ? holders.length : 0;
检测 是否使用 partial
的功能
if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
var partialsRight = partials,
holdersRight = holders;
partials = holders = undefined;
}
data
置为 弱引用 metaMap
取 func
的 value
值
var data = isBindKey ? undefined : getData(func);
初始化 newData
,newData
包含了被柯里化的函数,标志位,this 指针,绑定的参数,占位符,绑定的右起的参数,右起的占位符
var newData = [
func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
argPos, ary, arity
];
if (data) {
mergeData(newData, data);
}
func = newData[0];
bitmask = newData[1];
thisArg = newData[2];
partials = newData[3];
holders = newData[4];
arity = newData[9] = newData[9] === undefined
? (isBindKey ? 0 : func.length)
: nativeMax(newData[9] - length, 0);
根据 bitmask
使用对应的功能,这里使用的是 WRAP_CURRY_FLAG
if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
}
if (!bitmask || bitmask == WRAP_BIND_FLAG) {
var result = createBind(func, bitmask, thisArg);
} else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
result = createCurry(func, bitmask, arity);
} else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
result = createPartial(func, bitmask, thisArg, partials);
} else {
result = createHybrid.apply(undefined, newData);
}
var setter = data ? baseSetData : setData;
设置 toString
方法,对返回的函数的 toString
方法进行设置,设置为返回 原函数的 +
带有 /* [wrapped with _.curry] */
标识的字符串
return setWrapToString(setter(result, newData), func, bitmask);
3. creatCurry
1. 传入参数
func
传入的被柯里化的函数bitmask
标志位arity
传入给createCurry
的需要绑定的参数
2. 源码分析
过滤 placeholder
,同时返回柯里化的函数
function createCurry(func, bitmask, arity) {
var Ctor = createCtor(func);
function wrapper() {
var length = arguments.length,
args = Array(length),
index = length,
placeholder = getHolder(wrapper);
while (index--) {
args[index] = arguments[index];
}
var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
? []
: replaceHolders(args, placeholder);
length -= holders.length;
if (length < arity) {
return createRecurry(
func, bitmask, createHybrid, wrapper.placeholder, undefined,
args, holders, undefined, undefined, arity - length);
}
var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
return apply(fn, this, args);
}
return wrapper;
}
提取 func
作为构造函数,如果柯里化的是构造函数,也可以保证其正常接收参数并返回
var Ctor = createCtor(func);
初始化,获取 placeholder
,这里的 placeholder
可以是 lodash 默认的,也可以是在 wrapper
上设置的
var length = arguments.length,
args = Array(length),
index = length,
placeholder = getHolder(wrapper);
如果传入参数小于函数参数 arity
,调用 createRecurry
,返回新的函数
if (length < arity) {
return createRecurry(
func, bitmask, createHybrid, wrapper.placeholder, undefined,
args, holders, undefined, undefined, arity - length);
}
参数大于等于 arity
,传入参数返回函数调用结果,这里检测是否是构造函数,只要参数足够,柯里化的函数也可以作为构造函数使用,如果不是构造函数就正常返回调用值
var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
return apply(fn, this, args);
4. createRecurry
1. 传入参数
类似 creatCurry
2. 源码分析
wrapper
函数会调用 createRecurry
最终处理绑定的参数和 懒加载 等情况,然后交给 createHybrid
返回新函数或是返回结果
function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
// 根据 bitmask 和 isCurry 标志位,设置 holders 、 partials 等参数
var isCurry = bitmask & WRAP_CURRY_FLAG,
newHolders = isCurry ? holders : undefined,
newHoldersRight = isCurry ? undefined : holders,
newPartials = isCurry ? partials : undefined,
newPartialsRight = isCurry ? undefined : partials;
bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG);
bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);
// 清除 bind 标志位
if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
}
var newData = [
func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
newHoldersRight, argPos, ary, arity
];
// 调用 createHybird,借用这个函数的能力返回一个新函数
var result = wrapFunc.apply(undefined, newData);
if (isLaziable(func)) {
setData(result, newData);
}
// 设置 placeholder
result.placeholder = placeholder;
// 同样的,将 toString 设置为返回 原函数的 + 带有 /* [wrapped with _.curry] */ 标识的字符串
return setWrapToString(result, func, bitmask);
}
5. createHybrid
1. 传入参数
类似 createCurry
2. 源码分析
主要作用就是返回 wrapper
,类似于 createCurry
,返回一个 wrapper
函数
function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
var isAry = bitmask & WRAP_ARY_FLAG,
isBind = bitmask & WRAP_BIND_FLAG,
isBindKey = bitmask & WRAP_BIND_KEY_FLAG,
isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG),
isFlip = bitmask & WRAP_FLIP_FLAG,
Ctor = isBindKey ? undefined : createCtor(func);
function wrapper() {
var length = arguments.length,
args = Array(length),
index = length;
while (index--) {
args[index] = arguments[index];
}
if (isCurried) {
var placeholder = getHolder(wrapper),
holdersCount = countHolders(args, placeholder);
}
if (partials) {
args = composeArgs(args, partials, holders, isCurried);
}
if (partialsRight) {
args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
}
length -= holdersCount;
if (isCurried && length < arity) {
var newHolders = replaceHolders(args, placeholder);
return createRecurry(
func, bitmask, createHybrid, wrapper.placeholder, thisArg,
args, newHolders, argPos, ary, arity - length
);
}
var thisBinding = isBind ? thisArg : this,
fn = isBindKey ? thisBinding[func] : func;
length = args.length;
if (argPos) {
args = reorder(args, argPos);
} else if (isFlip && length > 1) {
args.reverse();
}
if (isAry && ary < length) {
args.length = ary;
}
if (this && this !== root && this instanceof wrapper) {
fn = Ctor || createCtor(fn);
}
return fn.apply(thisBinding, args);
}
return wrapper;
}
这里的 wrapper
其实类似上面的 createCurry
,增加了绑定之前参数的代码
function wrapper() {
var length = arguments.length,
args = Array(length),
index = length;
while (index--) {
args[index] = arguments[index];
}
if (isCurried) {
var placeholder = getHolder(wrapper),
holdersCount = countHolders(args, placeholder);
}
if (partials) {
args = composeArgs(args, partials, holders, isCurried);
}
if (partialsRight) {
args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
}
length -= holdersCount;
if (isCurried && length < arity) {
var newHolders = replaceHolders(args, placeholder);
return createRecurry(
func, bitmask, createHybrid, wrapper.placeholder, thisArg,
args, newHolders, argPos, ary, arity - length
);
}
var thisBinding = isBind ? thisArg : this,
fn = isBindKey ? thisBinding[func] : func;
length = args.length;
//......
if (isAry && ary < length) {
args.length = ary;
}
if (this && this !== root && this instanceof wrapper) {
fn = Ctor || createCtor(fn);
}
return fn.apply(thisBinding, args);
}
应用场景
curry
函数可以包裹现有的函数,用于复用函数,比如一个 add 函数,可以通过柯里化的方式包裹,形成了新的函数
const add =_.curry(add)
let add10 = add(10)
let add100 = add(100)
总结
柯里化的实现,用到了 JS 的闭包特性,存储了传入的参数,闭包通常指的是自带执行环境的函数。
柯里化虽然提升了代码的复用率,但也有问题,比如对执行逻辑的复杂化,比如在内存中产生了很多闭包
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!