lodash源码学习curry,curryRight
_.curry(func, [arity=func.length])
柯里化函数
关于函数柯里化,说简单点就是将接受多个参数的方法,转化为接受单一参数的方法,具体分析以及它的作用可以查看这篇文章http://www.zhangxinxu.com/wordpress/2013/02/js-currying/,
先看lodash中curry方法的源码
//curry.js var createWrap = require('./_createWrap');//函数包装方法 var WRAP_CURRY_FLAG = 8; /** * * * _.curry.placeholder的值默认为`_` * * @param {Function} func 需要柯里化的函数. * @param {number} [arity=func.length] 参数数量. * @param- {Object} [guard] 是否能作为遍历参数被像_.map这样的方法调用. * @returns {Function} 返回柯里化之后的函数. * @example * * var abc = function(a, b, c) { * return [a, b, c]; * }; * * var curried = _.curry(abc); * * curried(1)(2)(3); * // => [1, 2, 3] * * curried(1, 2)(3); * // => [1, 2, 3] * * curried(1, 2, 3); * // => [1, 2, 3] * * // Curried with placeholders. * curried(1)(_, 3)(2); * // => [1, 2, 3] */ function curry(func, arity, guard) { arity = guard ? undefined : arity; //调用createWrap方法,传入位掩码标识为curry var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity); result.placeholder = curry.placeholder; return result;//返回包装后的方法 } // 默认占位符赋值. curry.placeholder = {}; module.exports = curry;
这个方法依赖于createWrap方法,由于这个方法是很多方法的基础包装方法,所有依然只分析对应部分。
//_createWrap.js var baseSetData = require('./_baseSetData'), createBind = require('./_createBind'), createCurry = require('./_createCurry'), createHybrid = require('./_createHybrid'), createPartial = require('./_createPartial'), getData = require('./_getData'), mergeData = require('./_mergeData'), setData = require('./_setData'), setWrapToString = require('./_setWrapToString'), toInteger = require('./toInteger'); var FUNC_ERROR_TEXT = 'Expected a function'; //各种方法的位掩码标识 var WRAP_BIND_FLAG = 1, WRAP_BIND_KEY_FLAG = 2, WRAP_CURRY_FLAG = 8, WRAP_CURRY_RIGHT_FLAG = 16, WRAP_PARTIAL_FLAG = 32, WRAP_PARTIAL_RIGHT_FLAG = 64; var nativeMax = Math.max;//原生最大值方法 /** * 创建一个函数,该函数可以创建或调用func用可选的this和部分应用的参数. * * @param {Function|string} func 需要包装的函数. * @param {number} bitmask 位掩码标识 * @param {*} [thisArg] func的this对象 * @param {Array} [partials] 应用的参数 * @param {Array} [holders] 占位符的索引 * @param {Array} [argPos] . * @param {number} [ary] . * @param {number} [arity] 可用参数数量. * @returns {Function} 返回包装之后的函数. */ //lodash使用BitMask来进行各种方法的表示,BitMask使用方法可以看 http://geek.****.net/news/detail/73343 function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) { var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;//是否是bindKey方法(不是) if (!isBindKey && typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } var length = partials ? partials.length : 0;//应用的参数个数,为0 if (!length) { bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);//清除WRAP_PARTIAL_FLAG和WRAP_PARTIAL_RIGHT_FLAG partials = holders = undefined;//部分参数和占位符索引都为undefined } ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0); arity = arity === undefined ? arity : toInteger(arity);//将arity转为整形 length -= holders ? holders.length : 0;//跳过 if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {//是否是WRAP_PARTIAL_RIGHT_FLAG(不是,跳过) var partialsRight = partials, holdersRight = holders; partials = holders = undefined; } var data = isBindKey ? undefined : getData(func);//得到func的元数据 var newData = [ func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity ];//将所有参数赋值给newData 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);//处理一下arity,默认为func的长度 if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {//如果没有arity,清除curry和curryRight标识 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) {//如果传了arity,进入这里,调用createCurry方法 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;//设置data的元数据(暂不分析) return setWrapToString(setter(result, newData), func, bitmask);//给包装之后的方法添加元数据(用于优化),添加toStirng方法,并返回func(具体实现暂不分析) } module.exports = createWrap;
这个方法依赖于createCurry方法
//_createCurry.js var apply = require('./_apply'),//同Function.apply createCtor = require('./_createCtor'),//创建一个可以创建函数实例的方法 createHybrid = require('./_createHybrid'),//混合包装方法 createRecurry = require('./_createRecurry'),//createRecurry方法 getHolder = require('./_getHolder'),//得到占位符 replaceHolders = require('./_replaceHolders'),//替换占位符 root = require('./_root');//根元素,浏览器为window,node为global /** * 创建一个包装func,使其实现柯里化的方法. * * @private * @param {Function} func The function to wrap. * @param {number} bitmask The bitmask flags. See `createWrap` for more details. * @param {number} arity The arity of `func`. * @returns {Function} Returns the new wrapped function. */ 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数组 args[index] = arguments[index]; } var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder) ? [] : replaceHolders(args, placeholder);//得到占位符索引 length -= holders.length;//参数个数减去占位符的个数 if (length < arity) {//如果参数个数少于arity,调用createRecurry,并返回得到的新的函数 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);//调用fn } return wrapper; } module.exports = createCurry;
如果参数未传完,调用createRecurry方法
//_createRecurry.js var isLaziable = require('./_isLaziable'),//是否是一个惰性函数(在缓存中是否存在) setData = require('./_setData'),//设置元数据 setWrapToString = require('./_setWrapToString');//添加toString方法 //各种位掩码 var WRAP_BIND_FLAG = 1, WRAP_BIND_KEY_FLAG = 2, WRAP_CURRY_BOUND_FLAG = 4, WRAP_CURRY_FLAG = 8, WRAP_PARTIAL_FLAG = 32, WRAP_PARTIAL_RIGHT_FLAG = 64; /** * 创建一个包装func使其持续柯里化的方法. * * @private * @param {Function} func 需要包装的方法. * @param {number} bitmask 位掩码标识. * @param {Function} wrapFunc 包装函数. * @param {*} placeholder 占位符的值. * @param {*} [thisArg] . * @param {Array} [partials] 提前传入的参数. * @param {Array} [holders] 占位符索引. * @param {Array} [argPos] The argument positions of the new function. * @param {number} [ary] The arity cap of `func`. * @param {number} [arity] 参数个数. * @returns {Function} 返回新的包装方法. */ function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) { var isCurry = bitmask & WRAP_CURRY_FLAG,//是curry 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);//添加partial标识 bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);//清除partialRight标识 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 ]; var result = wrapFunc.apply(undefined, newData);//调用wrapFunc方法 if (isLaziable(func)) {//如果在缓存中存在,设置元数据 setData(result, newData); } result.placeholder = placeholder; return setWrapToString(result, func, bitmask);//添加toString方法并返回包装函数 } module.exports = createRecurry;
这个方法的wrapfunc也就是createHybrid
//_createHybrid.js var composeArgs = require('./_composeArgs'),//组合参数方法,见源码学习(7) composeArgsRight = require('./_composeArgsRight'), countHolders = require('./_countHolders'), createCtor = require('./_createCtor'), createRecurry = require('./_createRecurry'), getHolder = require('./_getHolder'), reorder = require('./_reorder'), replaceHolders = require('./_replaceHolders'), root = require('./_root'); //位掩码标识 var WRAP_BIND_FLAG = 1, WRAP_BIND_KEY_FLAG = 2, WRAP_CURRY_FLAG = 8, WRAP_CURRY_RIGHT_FLAG = 16, WRAP_ARY_FLAG = 128, WRAP_FLIP_FLAG = 512; /** * 创建一个包装函数,调用func使用可选的thisArg,应用部分参数和柯里化. * * @param {Function|string} func 需要包装的方法. * @param {number} bitmask 位掩码标识 * @param {*} [thisArg] . * @param {Array} [partials] 实现传入的参数. * @param {Array} [holders] 占位符. * @param {Array} [partialsRight] . * @param {Array} [holdersRight] . * @param {Array} [argPos] . * @param {number} [ary] . * @param {number} [arity] 可用函数参数数量. * @returns {Function} 返回新的包装函数. */ 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),//是curry方法 isFlip = bitmask & WRAP_FLIP_FLAG, Ctor = isBindKey ? undefined : createCtor(func); function wrapper() { var length = arguments.length,//参数个数 args = Array(length),//保存所有参数 index = length;//参数数组索引 while (index--) {//遍历参数,将所有参数存入args args[index] = arguments[index]; } if (isCurried) { var placeholder = getHolder(wrapper),//得到占位符标识 holdersCount = countHolders(args, placeholder);//占位符数量 } if (partials) {//执行composeArgs,对参数进行组合 args = composeArgs(args, partials, holders, isCurried); } if (partialsRight) { args = composeArgsRight(args, partialsRight, holdersRight, isCurried); } length -= holdersCount;//参数个数减去占位符数量 if (isCurried && length < arity) {//如果个数比arity小 var newHolders = replaceHolders(args, placeholder);//得到新的占位符索引位置 return createRecurry( func, bitmask, createHybrid, wrapper.placeholder, thisArg, args, newHolders, argPos, ary, arity - length );//再次调用createRecurry方法,继续柯里化(会一直循环调用createRecurry和createHybrid方法,直到参数传完整) } 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);//调用fn,并且传入args } return wrapper;//返回包装方法 } module.exports = createHybrid;
_.curryRight(func, [arity=func.length])
很_.curry很像,除了它接受参数从func的最后一个参数开始
这个方法和curry基本上实现是差不多的,只有细微的差别
//curryRight.js var createWrap = require('./_createWrap');//包装方法 var WRAP_CURRY_RIGHT_FLAG = 16;//curryRight位掩码标识 /** * * * _.curryRight.placeholder默认值为`_` * * @param {Function} func 需要柯里化的函数. * @param {number} [arity=func.length] 参数数量. * @param- {Object} [guard] 是否能作为遍历参数被像_.map这样的方法调用. * @returns {Function} 返回柯里化之后的函数. * @example * * var abc = function(a, b, c) { * return [a, b, c]; * }; * * var curried = _.curryRight(abc); * * curried(3)(2)(1); * // => [1, 2, 3] * * curried(2, 3)(1); * // => [1, 2, 3] * * curried(1, 2, 3); * // => [1, 2, 3] * * // Curried with placeholders. * curried(3)(1, _)(2); * // => [1, 2, 3] */ function curryRight(func, arity, guard) {//除了传入位掩码的不同和curry一模一样 arity = guard ? undefined : arity; var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity); result.placeholder = curryRight.placeholder; return result; } // Assign default placeholders. curryRight.placeholder = {}; module.exports = curryRight;
调用步骤和curry类似,看不同的部分
createRecurry
//_createRecurry.js var isLaziable = require('./_isLaziable'),//是否是一个惰性函数(在缓存中是否存在) setData = require('./_setData'),//设置元数据 setWrapToString = require('./_setWrapToString');//添加toString方法 //各种位掩码 var WRAP_BIND_FLAG = 1, WRAP_BIND_KEY_FLAG = 2, WRAP_CURRY_BOUND_FLAG = 4, WRAP_CURRY_FLAG = 8, WRAP_PARTIAL_FLAG = 32, WRAP_PARTIAL_RIGHT_FLAG = 64; /** * 创建一个包装func使其持续柯里化的方法. * * @private * @param {Function} func 需要包装的方法. * @param {number} bitmask 位掩码标识. * @param {Function} wrapFunc 包装函数. * @param {*} placeholder 占位符的值. * @param {*} [thisArg] . * @param {Array} [partials] 提前传入的参数. * @param {Array} [holders] 占位符索引. * @param {Array} [argPos] The argument positions of the new function. * @param {number} [ary] The arity cap of `func`. * @param {number} [arity] 参数个数. * @returns {Function} 返回新的包装方法. */ function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) { var isCurry = bitmask & WRAP_CURRY_FLAG,//不是curry newHolders = isCurry ? holders : undefined, newHoldersRight = isCurry ? undefined : holders,//设置newHoldersRight newPartials = isCurry ? partials : undefined, newPartialsRight = isCurry ? undefined : partials;//设置newPartialsRight bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG);//添加partialRight标识 bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);//清除partial标识 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 ]; var result = wrapFunc.apply(undefined, newData);//调用wrapFunc方法 if (isLaziable(func)) {//如果在缓存中存在,设置元数据 setData(result, newData); } result.placeholder = placeholder; return setWrapToString(result, func, bitmask);//添加toString方法并返回包装函数 } module.exports = createRecurry;
createHybrid
//_createHybrid.js var composeArgs = require('./_composeArgs'),//组合参数方法 composeArgsRight = require('./_composeArgsRight'),//从右向左组合参数 countHolders = require('./_countHolders'), createCtor = require('./_createCtor'), createRecurry = require('./_createRecurry'), getHolder = require('./_getHolder'), reorder = require('./_reorder'), replaceHolders = require('./_replaceHolders'), root = require('./_root'); //位掩码标识 var WRAP_BIND_FLAG = 1, WRAP_BIND_KEY_FLAG = 2, WRAP_CURRY_FLAG = 8, WRAP_CURRY_RIGHT_FLAG = 16, WRAP_ARY_FLAG = 128, WRAP_FLIP_FLAG = 512; /** * 创建一个包装函数,调用func使用可选的thisArg,应用部分参数和柯里化. * * @param {Function|string} func 需要包装的方法. * @param {number} bitmask 位掩码标识 * @param {*} [thisArg] . * @param {Array} [partials] 实现传入的参数. * @param {Array} [holders] 占位符. * @param {Array} [partialsRight] . * @param {Array} [holdersRight] . * @param {Array} [argPos] . * @param {number} [ary] . * @param {number} [arity] 可用函数参数数量. * @returns {Function} 返回新的包装函数. */ 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),//是curry方法 isFlip = bitmask & WRAP_FLIP_FLAG, Ctor = isBindKey ? undefined : createCtor(func); function wrapper() { var length = arguments.length,//参数个数 args = Array(length),//保存所有参数 index = length;//参数数组索引 while (index--) {//遍历参数,将所有参数存入args args[index] = arguments[index]; } if (isCurried) { var placeholder = getHolder(wrapper),//得到占位符标识 holdersCount = countHolders(args, placeholder);//占位符数量 } if (partials) { args = composeArgs(args, partials, holders, isCurried); } if (partialsRight) {//执行composeArgsRight方法,组合参数 args = composeArgsRight(args, partialsRight, holdersRight, isCurried); } length -= holdersCount;//参数个数减去占位符数量 if (isCurried && length < arity) {//如果个数比arity小 var newHolders = replaceHolders(args, placeholder);//得到新的占位符索引位置 return createRecurry( func, bitmask, createHybrid, wrapper.placeholder, thisArg, args, newHolders, argPos, ary, arity - length );//再次调用createRecurry方法,继续柯里化(会一直循环调用createRecurry和createHybrid方法,直到参数传完整) } 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);//调用fn,并且传入args } return wrapper;//返回包装方法 } module.exports = createHybrid;
composeArgsRight
//_composeArgsRight.js var nativeMax = Math.max;//原生求最大值方法 /** * 和composeArgs很像,只是组合参数的方向相反 * * @private * @param {Array} args 提供的参数. * @param {Array} partials 提前传入的参数. * @param {Array} holders 提前传入参数中的占位符索引. * @params {boolean} [isCurried] 指定是否组成一个柯里化函数. * @returns {Array} 返回新的参数集合. */ function composeArgsRight(args, partials, holders, isCurried) { var argsIndex = -1,//参数索引 argsLength = args.length,//传入参数个数 holdersIndex = -1,//占位符索引 holdersLength = holders.length,//占位符个数 rightIndex = -1,//提前传入参数索引 rightLength = partials.length,//提前传入参数个数 rangeLength = nativeMax(argsLength - holdersLength, 0),//实际传入参数个数 result = Array(rangeLength + rightLength),//结果参数数组 isUncurried = !isCurried; while (++argsIndex < rangeLength) {//遍历传入参数,将其传入result result[argsIndex] = args[argsIndex]; } var offset = argsIndex; while (++rightIndex < rightLength) {//遍历partials,将其传入result result[offset + rightIndex] = partials[rightIndex]; } while (++holdersIndex < holdersLength) {//遍历占位符索引,将result中对应索引的值,换成传入的参数对应的值 if (isUncurried || argsIndex < argsLength) { result[offset + holders[holdersIndex]] = args[argsIndex++]; } } return result;//返回生成的完整参数数组 } module.exports = composeArgsRight;
至此lodash中的函数柯里化分析完毕。