Exploring the streaming call of underscore

introduction
The

underscore is a JavaScript tool library that provides a full range of functional programming capabilities. It contains many utility functions such as each, Map, Reduce,filter, and so on. While es5,es6 already includes most of these, looking at the source code to see the underlying implementation of these functions will help you gain a deeper understanding of native js
The

underscore is defined as pure functions and supports chained calls to a one-way data source. In the chain of functions, data flows magically from one function to another as if passing through a pipe.

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

operation process is as follows:

  • takes the array as the starting data source, runs _([1, 2, 3, 4, 5, 6, 7]) to generate instance objects, and then calls the method chain on the instance to enable it to chain call.
  • runs the map function and multiplies each element of the array by 2 to form a new array. 14], filter out the value of less than 10
  • value () method to obtain the final run results [12, 14]

    the above process shows that the data source enters from the initial port and is passed back layer by layer. Each function will receive the result processed by the previous function and send the result run by itself to the next function. The data flows back like a stream of water as it enters a pipe, forming a streaming call. Next, implement its overall operation mechanism.

    source implementation

    create constructor

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

    implements streaming calls

    defines an object allExports, assigns the defined utility functions to the properties of the object, and passes in 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);
    

    runs the mixin function, and the parameter obj is the allExports defined above. Key is the name of the function,func corresponds to the specific function.

    func is not bound directly to the constructor, but to the constructor’s prototype object. As can be seen from here,_(data).chain().map().filter() calls chain,map,filter () during the execution process, it actually calls the function

    defined in the mixin mounted on the prototype object

    is executed as follows :

    • assumption data = [1, 2, 3], _ (data) is the result of instance object {wrapped: [1, 2, 3]}
    • call instance object chain method, below the running function. The result = [[1, 2, 3]].
    • func points to the chain function and adds _chain attribute to the instance object.
    • chainResult function determines that the current instance object branch does not support chain calls. If it supports subsequent newly generated instance objects, _chain is added as true. Return the new instance object
    • the data of the new instance object is still {wrapped:[1,2,3]}. Func points to the map function. Map function returns result [2,4,6]. ChainResult finds support for chain call run _([2,4,6]) to generate a new instance object
    • . At this point, this.wrapped has become [2,4,6]. It can be seen from here that every time a function is run, it will generate new instance object by taking the data processed by the function as parameter, and return this new instance object to continue calling other functions, and generate new instance object again. The new instance object continues to call with the processed data, forming the data flow.
    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);
    
    

    Read More: