最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • lodash 源码解析 -- curry,函数式编程利器

    正文概述 掘金(zer0fire)   2021-03-29   1393

    前言

    柯里化是函数式编程中不可或缺的一环,这个函数使得接受多参数的函数可以通过不影响结果的方式使其成为接受单参数的函数及其序列,根据这个特点可以合成出很多类似功能的函数,甚至是以自定义的顺序运行代码,达到复用代码的目的

    lodash 中的 curry 函数因为处理了很多边界情况,这里有些我也不是很清楚。因此这次我只会分析基础的 curry 相关的代码,其余的部分不会详细说明,有一些不足欢迎在评论里补充

    思路分析

    1. 流程图

    lodash 源码解析 -- curry,函数式编程利器

    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);
      }
    

    partialsundefinedlength 置为 0

    var length = partials ? partials.length : 0;
    

    如果 length0,把 partialpartialRight 功能也关闭,同时 holderspartials 置为 0aryarity 和传入时保持一致

    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 置为 弱引用 metaMapfuncvalue

    var data = isBindKey ? undefined : getData(func);
    

    初始化 newDatanewData 包含了被柯里化的函数,标志位,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 的闭包特性,存储了传入的参数,闭包通常指的是自带执行环境的函数。

    柯里化虽然提升了代码的复用率,但也有问题,比如对执行逻辑的复杂化,比如在内存中产生了很多闭包


    下载网 » lodash 源码解析 -- curry,函数式编程利器

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元