preface

The learning underscore. Js source code version is 1.13.1

_.extend

_.extend(destination, *sources) simply overwrites all the properties in the source object onto the Destination object and returns the Destination object. Copying is sequential, so subsequent object properties overwrite previous object properties (if there are duplicates)

_.extend(void 0, {a: 1});   // undefined
_.extend(null, {a: 1});  // null

_.extend({}, {a: 'b'});   // {a: 'b'}
_.extend({a: 'x'}, {a: 'b'});   // {a: 'b'}

_.extend({x: 'x'}, {a: 'a'.x: 2}, {a: 'b'});   // {x: 2, a: 'b'}

var F = function() {};
F.prototype = {a: 'b'};
var subObj = new F();
subObj.c = 'd';

_.extend({}, subObj);   // {c: 'd', a: 'b'}
Copy the code

How to do that?

// Get the names of all properties owned and inherited by obj
function allKeys(obj) {
  if(! isObject(obj))return [];
  var keys = [];
  for (var key in obj) keys.push(key);
  return keys;
}

/ * * * *@param {object} obj 
 */
function extend (obj) {
  var length = arguments.length;
  // if obj is null, undefined is returned
  if (length < 2 || obj == null) return obj;
  // Start with the second argument
  // You can also filter the parameters (filter out non-object parameters)
  for (var index = 1; index < length; index++) {
    var source = arguments[index],
        keys = allKeys(source),
        l = keys.length;
    for (var i = 0; i < l; i++) {
      varkey = keys[i]; obj[key] = source[key]; }}return obj;
}
Copy the code

_.extendOwn

ExtendOwn (destination, *sources) differs from extend in that only its own attributes are overridden to the target object

// Unlike extend, only subObj's own attributes are copied over the target object {}
_.extendOwn({}, subObj);    // {c: 'd'}
Copy the code

With extend above, implementation is easy

// Check whether the specified attribute exists in the object's own attributes
function has$1(obj, key) {
  returnobj ! =null && hasOwnProperty.call(obj, key);
}
var nativeKeys = Object.keys;
// Get the names of all enumerable properties on obj
function keys(obj) {
  if(! isObject(obj))return [];
  if (nativeKeys) return nativeKeys(obj);
  var keys = [];
  for (var key in obj) if (has$1(obj, key)) keys.push(key);
  return keys;
}

/ * * * *@param {object} obj 
 */
function extendOwn (obj) {
  var length = arguments.length;
  // if obj is null, undefined is returned
  if (length < 2 || obj == null) return obj;
  // Start with the second argument
  // You can also filter the parameters (filter out non-object parameters)
  for (var index = 1; index < length; index++) {
    var source = arguments[index],
        // this differs from extend
        keys = keys(source),
        l = keys.length;
    for (var i = 0; i < l; i++) {
      varkey = keys[i]; obj[key] = source[key]; }}return obj;
}
Copy the code

Since the two are so close, you can reuse them directly using a higher-order function that either receives a function as an argument or returns a function as output. Let’s look at one more _. Defaults function before implementing this higher-order function

_.defaults

_. Defaults (object, *defaults) Fills the undefined property of an object with defaults objects. And return this object. Once this property is filled, using the defaults method has no effect.

// The difference with extend is that undefined or nonexistent properties of object are padded
_.defaults({name: undefined.age: 18}, null.void 0, {name: 'zxx'.age: 20.sex: 'male'});   // {name: 'zxx', age: 18, sex: 'male'}

// The second difference is that null can be passed
_.defaults(null, {a: 1});   // {a: 1}
Copy the code

How to implement a higher-order function that returns a different function with different parameters

createAssigner

/** * return a different function * depending on the parameters passed@param {function} KeysFunc functions * that handle property names on obj@param {? boolean} Defaults is a defaults function */
function createAssigner(keysFunc, defaults) {
  return function(obj) {
    var length = arguments.length;
    // The defaults function obj can be null, creating a wrapper Object for the given argument through the Object constructor
    if (defaults) obj = Object(obj);
    if (length < 2 || obj == null) return obj;
    for (var index = 1; index < length; index++) {
      var source = arguments[index],
          keys = keysFunc(source),
          l = keys.length;
      for (var i = 0; i < l; i++) {
        var key = keys[i];
        // Compatibility processing
        // For extend, extendOwn! Defaults to true
        Obj does not exist in obj or its value is undefined for defaults functions
        if(! defaults || obj[key] ===void 0) obj[key] = source[key]; }}return obj;
  };
}

var extend = createAssigner(allKeys);

var extendOwn = createAssigner(keys);

var defaults = createAssigner(allKeys, true);
Copy the code

_.map

_.map(list, iteratee, [context]) generates an array corresponding to each element in the list by calling the iteratee iterator. Iteratee passes three arguments: value, then iterated index(or key), and finally a reference to the entire list using:

var arr = [1.2.3];
// A single object returns an array of values
_.map(arr);   / / [1, 2, 3]

_.map({1: 'one'.2: 'two'.3: 'three'});    // ['one', 'two', 'three']

_.map(undefined);   / / []

// When iteratee is a function, it is handled normally
_.map(arr, function(num){ return num * 3; });   / / [3, 6, 9]


var obj = [{name:'xman'.age: 20}, {name: 'zxx'.age: 18}];
// When iteratee is an object, returns whether the element matches the specified object
_.map(obj, {name: 'zxx'.age: 18});   // [false, true]


// When iteratee is a string or an array, return the set of attribute values corresponding to the element
_.map(obj, 'name');     // ['xman', 'zxx']
_.map(obj, ['age']);    / / [18] 20,

var obj1 = {
  name: 'zxx'.info: {
    age: 18}};// // pulls out deeper values
_.map(obj1, ['age']);    // [undefined, 18]


/ / in the context
_.map([1.2.3].function(item){
    return item + this.value;
}, {value: 100});     / / [101, 102, 103]
Copy the code

It seems that this function is really crazy, the following to write a version

Implement the first version

var MAX_ARRAY_INDEX = Math.pow(2.53) - 1;
// Check if it is a class array
var isArrayLike = function (obj) {
  let length = obj == null ? void 0 : obj.length;
  return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
}

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

function isObject(obj) {
  var type = typeof obj;
  return type === 'function' || type === 'object'&&!!!!! obj; }function map (obj, iteratee, context) {
  var_keys = ! isArrayLike(obj) && keys(obj), length = (_keys || obj).length, results =Array(length), iterateeFn;
  // Iteratee does not exist
  if (iteratee == null) {
    // Return the original value when iterating
    iterateeFn = function (value) {
      returnvalue; }}else if (isFunction(iteratee)) {  // iteratee is a function case
    iterateeFn = function () {
      return iteratee.apply(context, arguments); }}else if(isObject(iteratee) && ! isArray(iteratee)) {Iteratee is an object and not an array
    iterateeFn = (function () {
      var _keys = keys(iteratee), length = _keys.length;
      return function (obj) {
        for (var i = 0; i < length; i++) {
          var key = _keys[i];
          if(iteratee[key] ! == obj[key] || ! (keyin obj)) return false;
        }
        return true; }}}) ()else {  // Iteratee is an array or string, number, etc
    iterateeFn = (function () {
      var path = isArray(iteratee) ? iteratee: [iteratee], length = path.length;
      return function (obj) {
        for (var i = 0; i < length; i++) {
          if (obj == null) return void 0;
          // Fetch a deeper value based on the path
          obj = obj[path[i]];
        }
        return length ? obj : void 0; }}}) ()for (var index = 0; index < length; index++) {
    var currentKey = _keys ? _keys[index] : index;
    
    results[index] = iterateeFn(obj[currentKey], currentKey, obj);
  }
  return results;
}
Copy the code

The above code can basically meet the use, next we look at the source code:

function map(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;
}
Copy the code

cb

The cb function uses _. Iteratee. If you modify this function, you will actually affect multiple functions, which are basically set functions. These include map, find, filter, reject, every, some, Max, min, sortBy, groupBy, indexBy, countBy, sortedIndex, partition, and Unique

function cb(value, context, argCount) {
  Iteratee is used to handle value and context using our custom _. Iteratee function
  if(_ $1.iteratee ! == iteratee)return_ $1.iteratee(value, context);
  return baseIteratee(value, context, argCount);
}
Copy the code

iteratee

function iteratee(value, context) {
  return baseIteratee(value, context, Infinity);
}
Copy the code

baseIteratee

function baseIteratee(value, context, argCount) {
  // Handle the case where only one argument is passed to the map function
  if (value == null) return identity;
  // handle the function
  if (isFunction(value)) return optimizeCb(value, context, argCount);
  // Handles objects, not arrays
  if(isObject(value) && ! isArray(value))return matcher(value);
  return property(value);
}
Copy the code

identity

// Return the argument passed in
function identity(value) {
  return value;
}
Copy the code

This function may not seem very useful directly, but is easier to understand when you use it in an iterative function. Okay

optimizeCb

/** * underscore internal methods * returns some callback, iterative methods * based on the second operation of the this pointer (context argument) and argCount arguments@param {function} func 
 * @param {? *} context 
 * @param {? number} argCount 
 */
function optimizeCb(func, context, argCount) {
  // If this is not specified, the function is returned
  if (context === void 0) return func;
  switch (argCount == null ? 3 : argCount) {
    case 1: return function(value) {
      return func.call(context, value);
    };
    // If this is specified but no argCount argument is passed
    // methods like _. Each and _. Map go here
    case 3: return function(value, index, collection) {
      return func.call(context, value, index, collection);
    };
    // methods such as _. Reduce and _. ReduceRight will go here
    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

matcher

Returns an assertion function that gives you an assertion that can be used to tell if a given object matches the attrs specified key/value attribute

function matcher(attrs) {
  attrs = extendOwn({}, attrs);
  return function (obj) {
    return isMatch(obj, attrs);
  };
}
Copy the code

isMatch

_. IsMatch (Object, properties) tells you whether the keys and values in the properties are included in Object

/** * Determine whether the key and value in properties are included in object *@param {object} object 
 * @param {? object} attrs 
 */
function isMatch(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;
}

var moe = {name: 'Moe Howard'.hair: true};
var curly = {name: 'Curly Howard'.hair: false};

isMatch(moe, {hair: true});   // true
isMatch(curly, {name: 'zxx'});  // false
isMatch(null{});// true

function Prototest() {}
Prototest.prototype.x = 1;
var specObj = new Prototest;

isMatch({x: 2}, specObj);  // true

Copy the code

property

Returns a function that returns the specified property of any passed object. Path can be specified as a simple key, or as an array of object keys or array indexes for depth attribute extraction

function toPath$1(path) {
  returnisArray(path) ? path : [path]; } _ $1.toPath = toPath$1;

/** * internal method to get the value of the property in the object in depth *@param {object} obj 
 * @param {array|string|number} An array or simple key */ of the path object key or array index
function deepGet(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;
}

function toPath(path) {
  return_ $1.toPath(path);
}


function property(path) {
  path = toPath(path);
  return function(obj) {
    return deepGet(obj, path);
  };
}
Copy the code

conclusion

To learn the source code for underscore, you must touch on the CB and optimizeCb functions when analyzing collection-related functions. Mastering these two functions will help you understand the source code better and faster

Reference: github.com/mqyqingfeng…