Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lodash源码阅读(4)-lazyValue , 惰性求值? #4

Open
sevenCon opened this issue Jun 23, 2019 · 0 comments
Open

lodash源码阅读(4)-lazyValue , 惰性求值? #4

sevenCon opened this issue Jun 23, 2019 · 0 comments

Comments

@sevenCon
Copy link
Owner

sevenCon commented Jun 23, 2019

lodash惰性求值

什么叫惰性求值?

在编程语言理论中,惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”, 惰性求值的更多介绍

这个理论存编程语言层面, 如上所述, 有最小化求值, 和延迟求值的意思, 顾名思义, 非常容易理解.

关于惰性求值这里有一篇科普文

lodash 的惰性求值执行的流程

一个很平常的例子:

    var lo = _([11,12,23,41,5,6,17,8]);
    lo = lo.filter(item=>item>12);
    lo = lo.filter(item=>item>20)
    lo = lo.map(item=>30)
    var values = lo.value();
    console.log(values);

惰性求值是lodash的特性之一, 也是lodash一个比较喜人的特性, 可以有效地提高性能, 同时带来不一样的编程思考. 而它的实现也非常的简单, 思路是每一次调用lo.filter, lo.map等,都返回一个全新的lodash对象, 里面记录了所有的能够延迟求值的函数, 比如'filter', 'map', 'takeWhile'等等函数的调用.

lodash的lazyValue

调用value(); 即是调用lazyValue

image

那lazyValue 干了什么?

    function lazyValue() {
      var array = this.__wrapped__.value(),       // 初始的值
          dir = this.__dir__, // 遍历的方向, 默认从左开始
          isArr = isArray(array),
          isRight = dir < 0,
          arrLength = isArr ? array.length : 0,
          
          view = getView(0, arrLength, this.__views__),  
          // 如果存在take, drop等函数调用, 需要重新开始计算下面的start,end

          start = view.start,
          end = view.end,
          length = end - start,
          index = isRight ? end : (start - 1),
          iteratees = this.__iteratees__,   
          // 遍历的调用函数, 比如:filter(fn)中的fn, 就会push到这里

          iterLength = iteratees.length,
          resIndex = 0,
          takeCount = nativeMin(length, this.__takeCount__);
      
      // 这里如果是普通的调用, 没有take, drop等影响__wrapped__的元素
      if (!isArr || (!isRight && arrLength == length && takeCount == length)) {
        return baseWrapperValue(array, this.__actions__);
      }
      var result = [];
      
      // 这里如果有take, drop等因素重置了start, end, 那么会跑到这里
      // takeCount 也就是取值的个数, 比如take(3), 这个takeCount就是3
      // 然后遍历_wrapper__的数组, 取出符合所有iteratee的值, 个数为3的时候返回.
      outer:
      while (length-- && resIndex < takeCount) {
        index += dir;

        var iterIndex = -1,
            value = array[index];

        while (++iterIndex < iterLength) {
          var data = iteratees[iterIndex],
              iteratee = data.iteratee,
              type = data.type,
              computed = iteratee(value);
          
          // computed 是filter(fn), 中的fn的返回,
          // 如果是map, 则用map的返回作为value
          // 如果是filter, 不通过则返回outer的标签语句继续下一个index, 通过则进行复制到result
          if (type == LAZY_MAP_FLAG) {
            value = computed;
          } else if (!computed) {
            if (type == LAZY_FILTER_FLAG) {
              continue outer;
            } else {
              break outer;
            }
          }
        }
        result[resIndex++] = value;
      }
      return result;
    }

image

参数, 变量说明:

lazyValue函数就是开始执行了, 这里有几个主要的参数说明, 弄明白参数的作用, 那么这个函数的调用过程也就差不多

  • __wrapped__, 这个是_([1,2,3]), 或_.chain([1,2,3]), 传进来的数组, 也是我们需要操作的值
  • __dir__, 这个是方向, 可以从右开始遍历, 默认是从左到右遍历, 从索引0开始
  • __iteratees__, 这个是.filter(item=>item>1), fillter, map等等的函数回调函数, 是个数组, 储存lazyValue之前的所有的链式调用方法的回调.
  • getView(), 这个是计算调用了take, drop等之后受印象的start, end, 是为了判断有没有调用take, drop等方法, 而进入①/②等
  • start, 遍历的起始
  • end, 遍历的结束
  • takeCount, 需要返回或者遍历的长度

小结主要流程

  • 首先定义初始化的变量, 如上述
  • 如果没有调用take, takeRight,dropRight, 那么进入常规调用①
  • 判断如果有调用take, takeRight, drop, 等等, 那么进入②

需要区分开①, ②,的具体原因, 是因为常规的调用filter, map, find, 会产生串行的for循环, 如果其中有调用take(n), 那么如果只遍历一次, 然后检查当前元素是否满足所有的裁决函数, 只取n个, 就可以有效的降低for的串行调用, 更加高效, 如下图

image

上图来自这里

常规流程①的调用过程

常规的调用的, 就是一个个串行的filter,map,find,等等的遍历的数组.上一个的输入是当前的输入.当时这里有一点点绕, 因为filter, map,take, 在lodash上也是一个个有实际意义的方法, 所以要做一些判断,

  • baseWrapperValue(array, this.__actions__);
  • arrayReduce(actions, prediction, result)
    arrayReduce 迭代所有记录(filter,map,find等的调用, 类似reduce,上一个的输出是当前的输入. 完成后返回最后的结果
// 入口, 当调用.value()的时候, 
// actions 是所有filter, map, 等需要惰性求值的集合, 是个数组,
// value 是初始惰性求值计算的数组
function baseWrapperValue(value, actions) {
  var result = value;
  if (result instanceof LazyWrapper) {
    result = result.value();
  }

  // actions 就是filter, map, 等动作的集合
  return arrayReduce(actions, function(result, action) {
    // action.func 这个就是thru, 也就是intercepter的内容, 是lodashFunc, filter,map的真实执行,不是指记录map,filter的函数
    // 进行的动作是真实有效的filter,map等等
    // action.args是[interceptor], 这里执行的就是interceptor(value)
    return action.func.apply(action.thisArg, arrayPush([result], action.args));
  }, result);
}

/**
* 迭代arr, 每次都循环iteratee, 每次迭代的结果返回到accumulator, 最后作为结果返回
* 参数: array 是需要迭代的函数
* 参数: iteratee 是array[index] 封装过后的调用, 也就是lodash.filter的调用返回
*/
function arrayReduce(array, iteratee, accumulator, initAccum) {
var index = -1,
    length = array == null ? 0 : array.length;

if (initAccum && length) {
  accumulator = array[++index];
}
while (++index < length) {
  accumulator = iteratee(accumulator, array[index], index, array);
}
return accumulator;
}

// 这里是lodash初始化, 需要初始化filter,map,takeWhile的
// 一些函数, 区分filter, map, takeWhile的类型, 当调用filter等函数的时候, 记录到__iteratee__数组中
arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
  var type = index + 1,
      isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
  
 // 定义.filter , map, takeWhile 的函数
  LazyWrapper.prototype[methodName] = function(iteratee) {
    // 每次调用, 都clone当前lazyWrapper一份,  
    // 把当前的函数调用 push 进来然后返回
    var result = this.clone();             
    result.__iteratees__.push({
      'iteratee': getIteratee(iteratee, 3),
      'type': type
    });
    result.__filtered__ = result.__filtered__ || isFilter;
    return result;
  };
});

注: 本文的github地址为:lodash源码阅读(4)-lazyValue , 惰性求值?, 如有版权问题或其他问题, 请求给作者留言, 感谢!

@sevenCon sevenCon changed the title lodash的lazyValue , 惰性求值? lodash源码阅读(4)-lazyValue , 惰性求值? Jul 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant