preface

The countBy function creates an object that conditionally counts the value of some of the types in the first collection. The key of the object is the type of the value of the second iteratee. The corresponding value is the number of times each key of the first argument is returned after iteratee processes the second argument

Thought analysis

Source code analysis

1. countBy

1. Pass in parameters

Collection is passed a collection of iterators of type array, class array, plain object, etc. This can be interpreted as a condition for countBy aggregation

2. Source code analysis

CreateAggregator is called, and the count function is passed in. If result does not have a key, the baseAssignValue is initialized to 1. If result does have a key, the baseAssignValue is initialized to 1

var countBy = createAggregator(function(result, value, key) {
  if (hasOwnProperty.call(result, key)) {
    ++result[key];
  } else {
    baseAssignValue(result, key, 1); }});Copy the code

2. createAggregator

1. Pass in parameters
  • setteraccumulatorThe function that the accumulator processes,
  • initalizerInitializer function that initializes an accumulator for recording, default is an object
2. Source code analysis

An aggregator function is created, and initalizer is not passed in here so a new object is created as a record of the data by default. Using setter counts here the aggregator function internally determines whether the collection is an array. If it’s an array, use arrayAggregator, if it’s not, use baseAggregator. So I have a question here, why don’t I just do isArrayLike, so I don’t have to do baseAggregator again, And arrayAggregator is perfectly up to the task

function createAggregator(setter, initializer) {
  return function(collection, iteratee) {
    var func = isArray(collection) ? arrayAggregator : baseAggregator,
        accumulator = initializer ? initializer() : {};
    return func(collection, setter, getIteratee(iteratee, 2), accumulator);
  };
}
Copy the code

3. getIteratee

1. Pass in parameters
  • valueThe value to be processed by the iterator
  • arityThe number of iterators created
2. Source code analysis

Iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee: iteratee It is also one of the most complex functions in LoDash, and I’ll talk about how to use it later

function getIteratee() {
  var result = lodash.iteratee || iteratee;
  result = result === iteratee ? baseIteratee : result;
  return arguments.length ? result(arguments[0].arguments[1]) : result;
}
Copy the code

4. arrayAggregator

1. Pass in parameters
  • arrayRegular array
  • setterTo deal withaccumulatorThe function of
  • iterateeIterators, andcountByIs consistent with the iterator
  • accumulatorAccumulator, record the final result
2. Source code analysis

In setter counting, the accmulator is the object that counts, the key is the return value of iteratee(value), and the final return accumulator. The accmulator function internally conditions length and uses a while loop

function arrayAggregator(array, setter, iteratee, accumulator) {
  var index = -1,
      length = array == null ? 0 : array.length;
  while (++index < length) {
    var value = array[index];
    setter(accumulator, value, iteratee(value), array);
  }
  return accumulator;
}
Copy the code

5. baseAggregator

1. Pass in parameters
  • collectionClass array object or object
  • setterTo deal withaccumulatorThe function of
  • iterateeIterators, andcountByIs consistent with the iterator
  • accumulatorAccumulator, record the final result
2. Source code analysis

BaseEach determines whether the collection is an array of classes. If not, createBaseFor fetches all the keys in the collection and their total. A while loop iterates through all the keys to the value and returns the counter

function baseAggregator(collection, setter, iteratee, accumulator) {
  baseEach(collection, function(value, key, collection) {
    setter(accumulator, value, iteratee(value), collection);
  });
  return accumulator;
}
Copy the code

6. baseAssignValue

1. Pass in parameters
  • objectAn object
  • keyNeed to mergekey
  • valuePrevious parameterkeyValue of the correspondingvalue
2. Source code analysis

Merge attributes of __proto__ using defineProperty on the native object, and merge other values of the object directly with []

function baseAssignValue(object, key, value) {
  if (key == '__proto__' && defineProperty) {
    defineProperty(object, key, {
      'configurable': true.'enumerable': true.'value': value,
      'writable': true
    });
  } else{ object[key] = value; }}Copy the code

Application scenarios

Similarly, count the number of occurrences of the same number in the integer part of the array, and the number of occurrences of the same length of the string

_.countBy([6.1.4.2.6.3].Math.floor);
// => {'4': 1, '6': 2}
 
_.countBy(['one'.'two'.'three'].'length');
// => {'3': 2, '5': 1}
Copy the code

conclusion

CountBy passes in iteratee to filter out and count the key values according to certain conditions, which is an expression of functional programming. The traditional way of counting might be to create a new function to iterate over iteratee and then pass it into count. The idea of functional programming is to use countBy to pass iteratee directly as a condition and use a function to do the counting directly