Research on source code of underscore.js (3)

Overview

I have wanted to study the underscore source code for a long time. Although the underscore.js library is a bit outdated, I still want to learn about the library's architecture, functional programming and the writing of common methods, but there is nothing else to study. , so let's end the wish to study the underscore source code.

underscore.js source code research (1)
underscore.js source code research (2)
underscore.js source code research (3)
underscore.js source code research (4)
underscore.js source code research (5)
underscore.js source code research (6)
underscore. js source code research (7)
underscore.js source code research (8)

References: official notes of underscore.js , undersercore source code analysis , undersercore source code analysis segmentfault

functional programming

I haven't figured out why map is used instead of for loop. It turns out that map is iterative thinking in functional programming .

For an iteration, it consists of at least two parts: the iterated collection and the current iteration process . In underscore, the current iteration process is a function called iteratee , which processes the iterated collection. An example is as follows:

_.map = _.collect = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length,
        results = Array(length);
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      results[index] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
};

It can be seen that the iteratee function will be processed by the cb method first, and the cb method will process a function through optimizeCb . The original code is as follows:

var cb = function(value, context, argCount) {
    if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
    return _.property(value);
};

The code for optimizeCb is long:

var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

Although the code is very long, the optimizeCb method only has one effect: bind the context context to the func function. Those long cases are just the case where different parameters of func are returned as they are: func takes only one parameter , such as the iterative function of times; func takes three parameters , such as the iterative function of map, some, etc.; func takes four parameters , such as reduce, etc. iterative function.

Implementation of the remaining parameters

We often have such a requirement, that is, we want to make a function accept multiple parameters , but we do not need to enter all parameters when calling, we can achieve this:

function add(x, y, z) {
    z = z == null ? 0 : z;
    return x + y + z;
}

add(1,2,0); //输出3
add(1,2); //输出3

But what if we wanted to make add capable of accepting an infinite number of arguments? (This is very normal in functional programming) We can put the remaining parameters in a rest parameter:

function add(x, rest) {
    return _.reduce(rest,function(accum, current){
      return accum+current;
    },x);
}

add(1, [2, 3]); //输出6
add(1, [2, 3, 4]); //输出10
add(1, [2, 3, 4, 5]); //输出15

So the question becomes how to encapsulate add so that it add(1,2,3,4)will become add(1, [2,3,4])the form.

For this underscore, the restArguments method is used . The source code and comments are as follows:

var restArguments = function(func, startIndex) {
    //不输入startIndex则自动取最后一个为rest
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    //接受一个函数为参数,返回一个包装后的函数,参数用arguments获取
    return function() {
      var length = Math.max(arguments.length - startIndex, 0),
          rest = Array(length),
          index = 0;
      for (; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      switch (startIndex) {
        //原函数只接受一个rest参数
        case 0: return func.call(this, rest);
        //原函数接受1个参数 + rest参数
        case 1: return func.call(this, arguments[0], rest);
        //原函数接受2个参数 + rest参数
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      //原函数接受2个以上参数 + rest参数
      return func.apply(this, args);
    };
};

It is used like this:

var addWithRest = _.restArguments(add, 1);
addWithRest(1,2,3,4); //10

It is worth mentioning that es6 invented a syntax called Rest parameters . This syntax is more user-friendly than the above method, examples are as follows:

//...theArgs这种写法就是Rest parameters的写法
function sum(...theArgs) {
  return theArgs.reduce((previous, current) => {
    return previous + current;
  });
}

console.log(sum(1, 2, 3));
// expected output: 6

console.log(sum(1, 2, 3, 4));
// expected output: 10

Note : The restArguments method is the latest addition, and there is no restArguments method in the underscore of version 1.8.3 on the cdn.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324768965&siteId=291194637