preface

From the names cb and optimizeCb, you might not be able to guess what this is for, although you might think cb is short for callback.

If you go straight to the source code, you might wonder why, so we’ll start with the _. Map function.

_.map

_. Map is similar to array.prototype. map, but more robust and refined. Let’s look at the source of _. Map:

// Obj is an array
_.map = function (obj, iteratee, context) {
    iteratee = cb(iteratee, context);

    var length = obj.length, results = Array(length);
    for (var index = 0; index < length; index++) {
        results[index] = iteratee(obj[index], index, obj);
    }

    return results;
};Copy the code

In addition to passing in the Array to process, the map method takes iteratee and context, similar to the other two arguments in array.prototype. map, where iteratee represents the handler function and context represents the specified execution context. That’s the value of this.

Then, in the source code, we see that we pass iteratee and context into a cb function, override iteratee, and use that function as the final handler.

In fact, why bother? The iteratee function is used to process the values of each iteration. You specify the value of this by context. We can just write it like this:

_.map = function (obj, iteratee, context) {
    var length = obj.length, results = Array(length);
    for (var index = 0; index < length; index++) {
        results[index] = iteratee.call(context, obj[index], index, obj);
    }
    return results;
};

/ / [2, 3, 4]
console.log(_.map([1.2.3].function(item){
    return item + 1;
})) 

/ / [2, 3, 4]
console.log(_.map([1.2.3].function(item){
    return item + this.value;
}, {value: 1}))Copy the code

What if iteratee doesn’t pass a function? What if we pass nothing, or an object, or a string or a number?

What about underscore if our method automatically returns an error?

/ / use the underscore

// Nothing
var result = _.map([1.2.3]); / / [1, 2, 3]

// Pass in an object
var result = _.map([{name:'Kevin'}, {name: 'Daisy'.age: 18}, {name: 'Daisy'}); // [false, true]

var result = _.map([{name: 'Kevin'}, {name: 'Daisy'}].'name'); // ['Kevin', 'daisy']Copy the code

We’ll see that underscore can also achieve different effects depending on the type of value passed in. Let’s summarize:

  1. Returns the same array when iteratee is not transmitted.
  2. When iteratee is a function, it is handled normally.
  3. When iteratee is an object, returns whether the element matches the specified object.
  4. When iteratee is a string, return the set of attribute values corresponding to the element.

The cb functions underscore iteratee value types and return different Iteratee functions for each type.

cb

So let’s look at the cb function source:

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);
};Copy the code

There are eight functions involved! Don’t be afraid. Let’s look at them one by one.

_.iteratee

if(_.iteratee ! == builtinIteratee)return _.iteratee(value, context);Copy the code

Iteratee: iteratee: iteratee: iteratee

_.iteratee = builtinIteratee = function(value, context) {
    return cb(value, context, Infinity);
};Copy the code

Iteratee = builtinIteratee, iteratee! BuiltinIteratee == builtinIteratee value is false, so normally _. Iteratee (value, context) is not executed.

But if we modify the. Iteratee function externally, the result will be true, and cb will return. Iteratee (value, context).

Iteratee is our custom _. Iteratee function that handles the value and context.

Imagine that we don’t need the power of _. Map. I just want to use the array element when value is a function, or return the current element if it’s not a function.

<html>
<head>
    <title>underscore map</title>
</head>
<body>
    <script src=".. /vender/underscore.js"></script>
    <script type="text/javascript">
    _.iteratee = function(value, context) {
        if (typeof value === 'function') {
            return function(. rest) {
                returnvalue.call(context, ... rest) }; }return function(value) {
            return value;
        };
    };

    // If the second argument to map is not a function, that element is returned
    console.log(_.map([1.2.3].'name')); / / [1, 2, 3]

    // If the map's second argument is a function, that function is used to process array elements
    var result = _.map([1.2.3].function(item) {
        return item + 1;
    });

    console.log(result); / / [2, 3, 4]
    </script>
</body>
</html>Copy the code

Note that underscore functions use cb functions, and because cb functions use _. Iteratee, if you modify the underscore function, it will affect many functions. These functions are basically set functions, These include map, find, filter, reject, every, some, Max, min, sortBy, groupBy, indexBy, countBy, sortedIndex, partition, and Unique.

_.identity

if (value == null) return _.identity;Copy the code

Let’s look at _. Identity source code:

_.identity = function(value) {
    return value;
};Copy the code

This is why when the second parameter of map is passed nothing, the result is the same array.

_.map([1.2.3]); / / [1, 2, 3]Copy the code

If you look at this function directly, it doesn’t seem very useful, but in this case, it’s perfectly appropriate.

optimizeCb

if (_.isFunction(value)) return optimizeCb(value, context, argCount);Copy the code

When value is a function, we pass in the optimizeCb function.

var optimizeCb = function(func, context, argCount) {
    // If no context is passed, the func function is returned
    if (context === void 0) return func;
    switch (argCount) {
        case 1:
            return function(value) {
                return func.call(context, value);
            };
        case null:
        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);
    };
};Copy the code

You might be wondering, why am I judging argCount? Can’t you just go back? Like this:

var optimizeCb = function(func, context) {
    // If no context is passed, the func function is returned
    if (context === void 0) return func;
    return function() {
        return func.apply(context, arguments);
    };
};Copy the code

No problem, of course, but why does underscore do this? Arguments is just a way to avoid using arguments and improve performance, and there’s no need to do this if you’re not writing a library.

When there are 3 arguments, the names of the arguments are value, index, and collection, why is there no case where the arguments are 2? All underscore functions are used in cases where no function uses two parameters and therefore omitted, such as the map function, which uses three parameters and uses the names of the variables.

_.matcher

if(_.isObject(value) && ! _.isArray(value))return _.matcher(value);Copy the code

This is what happens when the second argument to a map is an object:

// Pass in an object
var result = _.map([{name:'Kevin'}, {name: 'Daisy'.age: 18}, {name: 'Daisy'}); // [false, true]Copy the code

If value is an object and not an array, the _. Matcher function is used. Look at the source of each function:

var nativeIsArray = Array.isArray;

_.isArray = nativeIsArray || function(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
};

_.isObject = function(obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object'&&!!!!! obj; };// Extend function can refer to "JavaScript topics handwriting a jQuery extend"
_.matcher = function(attrs) {
    attrs = _.extend({}, attrs);
    return function(obj) {
      return _.isMatch(obj, attrs);
    };
};

// This function determines whether the key values in the attr object have and are equal to those in the object

// var stooge = {name: 'moe', age: 32};
// _.isMatch(stooge, {age: 32}); => true

Keys = _.keys = object.keys
_.isMatch = function(object, attrs) {
    var keys = _.keys(attrs), length = keys.length;
    if (object == null) return! length;var obj = Object(object);
    for (var i = 0; i < length; i++) {
        var key = keys[i];
        if(attrs[key] ! == obj[key] || ! (keyin obj)) return false;
    }
    return true;
};Copy the code

_.property

return _.property(value);Copy the code

If value is a primitive type, return the value of the element’s attribute:

var result = _.map([{name: 'Kevin'}, {name: 'Daisy'}].'name'); // ['Kevin', 'daisy']Copy the code

Let’s look at the source code:

_.property = function(path) {
    // If it is not an array
    if(! _.isArray(path)) {return shallowProperty(path);
    }
    return function(obj) {
        return deepGet(obj, path);
    };
};

var shallowProperty = function(key) {
    return function(obj) {
        return obj == null ? void 0 : obj[key];
    };
};

// Fetch a deeper value based on the path
var deepGet = function(obj, path) {
    var length = path.length;
    for (var i = 0; i < length; i++) {
        if (obj == null) return void 0;
        obj = obj[path[i]];
    }
    return length ? obj : void 0;
};Copy the code

Value can also be passed as an array to fetch a deeper value. For example:

var person1 = {
    child: {
        nickName: 'Kevin'}}var person2 = {
    child: {
        nickName: 'Daisy'}}var result = _.map([person1, person2], ['child'.'nickName']); 
console.log(result) // ['Kevin', 'daisy']Copy the code

The last

If you want to learn about the source code for underscore, you must touch on the CB and optimizeCb functions while analyzing collection-related functions. Mastering these two functions first will help you understand the source code better and faster.

The underscore series

Underscore Catalogue Address: github.com/mqyqingfeng… .

The Underscore series is designed to help you read the source code and write your own undercore underscores, highlighting code architectures, chain calls, internal functions, and template engines.

If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.