探索underscore的流式调用

前言

underscore是一个JavaScript工具库,提供了一整套函数式编程的实用功能.其内部包含了很多工具函数比如each,map,reduce,filter等等.虽然es5,es6已经包含其中大部分,但查看源码了解这些函数底层的实现有助于加深对原生js的理解.

underscore内部定义的都是纯函数,并支持单向数据源的链式调用.在众多函数组成的链条中,可以神奇的发现数据就像经过管道一样从一个函数流向另一个函数.

  const result = _([1, 2, 3, 4, 5, 6, 7]).chain().map((item) => {return item * 2;}).filter((item) => {
        return item > 10;
        }).value();

运行过程如下:
  • 将数组作为起始数据源,运行_([1, 2, 3, 4, 5, 6, 7])生成underscore的实例对象,再调用实例上的方法chain使其具备链式调用能力
  • 运行map函数将数组每个元素都乘以2组成新数组返回
  • 运行filter函数时输入数据已经变成[2, 4, 6, 8, 10, 12, 14],过滤掉小于10的值
  • 最终运行value()方法获取结果[12,14]

以上过程看出,数据源从最初端口进入,一层层向后传递,每个函数会接收到前一个函数处理完毕的结果,又会向下一个函数发送自己运行完的结果.数据就像进入管道一样,像流水一般往后流动,如此便形成了流式调用.接下来实现它的整体运作机制.

源码实现

创建构造函数

定义构造函数_,如果obj是一条普通数据,运行_(obj) 时,this指向了window,结果返回实例对象 new _(obj),那么该对象包含属性wrapped,对应的值为obj.

(function (global) {

  function _(obj) {
    if (obj instanceof _) {
      return obj;
    }
    if (!(this instanceof _)) {
      return new _(obj);
    }
    this.wrapped = obj;
  }

  _.prototype.value = function () {
    return this.wrapped;
  };

  global._ = _;
  
})(window);

实现流式调用

定义一个对象allExports,将定义的工具函数赋予该对象的属性上,并传入mixin函数运行

function chain(obj) {
    const _instance = _(obj);
    _instance._chain = true;
    return _instance;
  }

  function map(obj, iteratee) {...}

  function filter(obj, iteratee) {...}

  const allExports = {
    chain,
    map,
    filter,
  };

  mixin(allExports);

运行mixin函数,参数obj便是上方定义的allExports.获取所有函数名组成数组array进行遍历.key是函数名,func对应具体的函数.

func并没有直接绑定在构造函数上,而是绑定在构造函数的原型对象上.从这里可以看出,_(data).chain().map().filter()执行过程里,调用chain,map,filter时其实调用的是定义在mixin里面挂载在原型对象上的函数

执行流程如下:

  • 假设data = [1,2,3],_(data)的结果便是实例对象{wrapped:[1,2,3]}
  • 调用实例对象的chain方法,运行下方函数.result = [[1,2,3]].使用push将用户可能传入的参数并入result中
  • func此时指向chain函数,运行chain函数给实例对象添加了_chain属性true.
  • chainResult函数判端当前实例对象支不支持链式调用,如果支持后续新生成的实例对象都添加上_chain为true.并返回此新实例对象
  • 新的实例对象的数据仍然是{wrapped:[1,2,3]}.继续调用map方法运行下方函数,将this.wrapped和用户传递的参数组合起来传入func,func指向了map函数.map函数运行完返回结果[2,4,6].chainResult发现支持链式调用运行_([2,4,6])生成新的实例对象
  • 新的实例对象继续调用filter,调用的仍然是下方函数.此时this.wrapped已经变成了[2,4,6].从这里可以看出每运行一个函数,会将函数处理完的数据作为参数生成新的实例对象,并返回此新实例对象继续调用其他函数,又会生成新的实例对象.新的实例对象带着处理后的数据继续调用下去,就形成了数据的流动.
function mixin(obj) {
    const array = Object.keys(obj);
    array.forEach((key) => {
      const func = obj[key];
      _.prototype[key] = function () {
        const result = [this.wrapped];
        Array.prototype.push.apply(result, arguments);
        return chainResult(this, func.apply(_, result));
      };
    });
  }

  function chainResult(_instance, obj) {
    return _instance._chain ? _(obj).chain() : obj;
  }

完整代码

(function (global) {
  function _(obj) {
    if (obj instanceof _) {
      return obj;
    }
    if (!(this instanceof _)) {
      return new _(obj);
    }
    this.wrapped = obj;
  }

  _.prototype.value = function () {
    return this.wrapped;
  };

  function chain(obj) {
    const _instance = _(obj);
    _instance._chain = true;
    return _instance;
  }
  
  //map函数的简单实现,支持数组和对象
  function map(obj, iteratee) {
    const keys = !Array.isArray(obj) && Object.keys(obj);
    const len = keys ? keys.length : obj.length;
    const result = [];
    for (let i = 0; i < len; i++) {
      const current_key = keys ? keys[i] : i;
      result.push(iteratee(obj[current_key]));
    }
    return result;
  }

  //filter函数的简单实现,,支持数组和对象
  function filter(obj, iteratee) {
    const keys = !Array.isArray(obj) && Object.keys(obj);
    const len = keys ? keys.length : obj.length;
    const result = [];
    for (let i = 0; i < len; i++) {
      const current_key = keys ? keys[i] : i;
      if (iteratee(obj[current_key])) {
        result.push(obj[current_key]);
      }
    }
    return result;
  }

  function mixin(obj) {
    const array = Object.keys(obj);
    array.forEach((key) => {
      const func = obj[key];
      _.prototype[key] = function () {
        const result = [this.wrapped];
        Array.prototype.push.apply(result, arguments);
        return chainResult(this, func.apply(_, result));
      };
    });
  }

  function chainResult(_instance, obj) {
    return _instance._chain ? _(obj).chain() : obj;
  }

  const allExports = {
    chain,
    map,
    filter,
  };

  mixin(allExports);

  global._ = _;
})(window);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值