Explore streaming calls of underscore

Preface

Underscore is a JavaScript tool library that provides a set of practical functions for functional programming. It contains many tool functions such as each, map, reduce, filter, etc. Although es5 and es6 already contain most of them, check the source code to understand The underlying implementation of these functions helps to deepen the understanding of native js.

The internal definitions of underscore are pure functions and support the chain call of one-way data sources. In the chain composed of many functions, it can be magically found that the data flows from one function to another function like a pipeline.

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

The running process is as follows:
  • Using the array as the starting data source, run _([1, 2, 3, 4, 5, 6, 7]) to generate an instance object of underscore, and then call the method chain on the instance to make it capable of chain calling
  • Run the map function to multiply each element of the array by 2 to form a new array and return
  • When running the filter function, the input data has become [2, 4, 6, 8, 10, 12, 14], and values ​​less than 10 are filtered out
  • Finally run the value() method to get the result [12,14]

The above process shows that the data source enters from the initial port and is passed layer by layer. Each function will receive the result processed by the previous function, and will send the result of its own operation to the next function. Data is like entering Like a pipe, it flows backwards like water, thus forming a streaming call. Next, realize its overall operating mechanism.

Source code implementation

Create a constructor

Define the constructor _, if obj is a piece of ordinary data, when running _(obj), this points to window, and the result returns the instance object new _(obj), then the object contains the attribute wrapped, and the corresponding value is 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);

Implement streaming calls

Define an object allExports, assign the defined tool function to the attribute of the object, and pass it into the mixin function to run

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

Run the mixin function, the parameter obj is the allExports defined above. Get all the function names to form an array array for traversal. The key is the function name, and the func corresponds to the specific function.

func is not directly bound to the constructor, but to the prototype object of the constructor. It can be seen from this that during the execution of _(data).chain().map().filter(), call When chain, map, and filter actually call the function defined in the mixin and mounted on the prototype object

The execution process is as follows:

  • Assuming data = [1,2,3], the result of _(data) is the instance object {wrapped:[1,2,3]}
  • Call the chain method of the instance object and run the function below. result = [[1,2,3]]. Use push to incorporate the parameters that may be passed by the user into the result
  • func now points to the chain function, and running the chain function adds the _chain attribute true to the instance object.
  • The chainResult function judges that the current instance object does not support chain calls. If it supports subsequent newly generated instance objects, add _chain to true. And return this new instance object
  • The data of the new instance object is still {wrapped:[1,2,3]}. Continue to call the map method to run the function below, combine this.wrapped and the parameters passed by the user into func, and func points to the map function.map The function returns the result after running [2,4,6].chainResult found that it supports chain call operation _([2,4,6]) to generate a new instance object
  • The new instance object continues to call filter, and the function below is still called. At this time this.wrapped has become [2,4,6]. It can be seen from this that every time a function is run, the data processed by the function will be used as The parameter generates a new instance object, and returns this new instance object to continue calling other functions, and then generates a new instance object. The new instance object continues to be called with the processed data, and the flow of data is formed.
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;
  }

Complete code

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

Guess you like

Origin blog.csdn.net/brokenkay/article/details/109105512