zl程序教程

您现在的位置是:首页 >  其它

当前栏目

_.differenceBy(array, [values], [iteratee=_.identity])

Array VALUES identity _.
2023-09-11 14:15:02 时间

5

_.differenceBy(array, [values], [iteratee=_.identity])

_.differenceBy方法类似于_.difference方法,它会接受一个迭代器函数参数,为数组和排除的数组的每个元素都用这个函数处理后再比较。

参数

array (Array): 用来检查的数组
[values] (...Array): 用来排除的数组
[iteratee=_.identity] (Function): 处理每一个元素的迭代器函数

返回值

(Array):返回一个包含过滤值的新数组

例子

_.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
// => [1.2]
 
// The `_.property` iteratee shorthand.
_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
// => [{ 'x': 2 }]

源代码:

/**
 * lodash (Custom Build) <https://lodash.com/>
 * Build: `lodash modularize exports="npm" -o ./`
 * Copyright jQuery Foundation and other contributors <https://jquery.org/>
 * Released under MIT license <https://lodash.com/license>
 * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
 * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 */

/** Used as the size to enable large array optimizations. */
var LARGE_ARRAY_SIZE = 200;

/** Used as the `TypeError` message for "Functions" methods. */
var FUNC_ERROR_TEXT = 'Expected a function';

/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED = '__lodash_hash_undefined__';

/** Used to compose bitmasks for comparison styles. */
var UNORDERED_COMPARE_FLAG = 1,
    PARTIAL_COMPARE_FLAG = 2;

/** Used as references for various `Number` constants. */
var INFINITY = 1 / 0,
    MAX_SAFE_INTEGER = 9007199254740991;

/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
    arrayTag = '[object Array]',
    boolTag = '[object Boolean]',
    dateTag = '[object Date]',
    errorTag = '[object Error]',
    funcTag = '[object Function]',
    genTag = '[object GeneratorFunction]',
    mapTag = '[object Map]',
    numberTag = '[object Number]',
    objectTag = '[object Object]',
    promiseTag = '[object Promise]',
    regexpTag = '[object RegExp]',
    setTag = '[object Set]',
    stringTag = '[object String]',
    symbolTag = '[object Symbol]',
    weakMapTag = '[object WeakMap]';

var arrayBufferTag = '[object ArrayBuffer]',
    dataViewTag = '[object DataView]',
    float32Tag = '[object Float32Array]',
    float64Tag = '[object Float64Array]',
    int8Tag = '[object Int8Array]',
    int16Tag = '[object Int16Array]',
    int32Tag = '[object Int32Array]',
    uint8Tag = '[object Uint8Array]',
    uint8ClampedTag = '[object Uint8ClampedArray]',
    uint16Tag = '[object Uint16Array]',
    uint32Tag = '[object Uint32Array]';

/** Used to match property names within property paths. */
var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
    reIsPlainProp = /^\w*$/,
    reLeadingDot = /^\./,
    rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;

/**
 * Used to match `RegExp`
 * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
 */
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;

/** Used to match backslashes in property paths. */
var reEscapeChar = /\\(\\)?/g;

/** Used to detect host constructors (Safari). */
var reIsHostCtor = /^\[object .+?Constructor\]$/;

/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;

/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
typedArrayTags[errorTag] = typedArrayTags[funcTag] =
typedArrayTags[mapTag] = typedArrayTags[numberTag] =
typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
typedArrayTags[setTag] = typedArrayTags[stringTag] =
typedArrayTags[weakMapTag] = false;

/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;

/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || Function('return this')();

/** Detect free variable `exports`. */
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;

/** Detect free variable `module`. */
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;

/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;

/** Detect free variable `process` from Node.js. */
var freeProcess = moduleExports && freeGlobal.process;

/** Used to access faster Node.js helpers. */
var nodeUtil = (function() {
  try {
    return freeProcess && freeProcess.binding('util');
  } catch (e) {}
}());

/* Node.js helper references. */
var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;

/**
 * A faster alternative to `Function#apply`, this function invokes `func`
 * with the `this` binding of `thisArg` and the arguments of `args`.
 *
 * @private
 * @param {Function} func The function to invoke.
 * @param {*} thisArg The `this` binding of `func`.
 * @param {Array} args The arguments to invoke `func` with.
 * @returns {*} Returns the result of `func`.
 */
//一个速度更快的apply方法
function apply(func, thisArg, args) {
  switch (args.length) {
    case 0: return func.call(thisArg);
    case 1: return func.call(thisArg, args[0]);
    case 2: return func.call(thisArg, args[0], args[1]);
    case 3: return func.call(thisArg, args[0], args[1], args[2]);
  }
  return func.apply(thisArg, args);
}

/**
 * A specialized version of `_.includes` for arrays without support for
 * specifying an index to search from.
 *
 * @private
 * @param {Array} [array] The array to inspect.
 * @param {*} target The value to search for.
 * @returns {boolean} Returns `true` if `target` is found, else `false`.
 */
//判断数组是否包含给定值
function arrayIncludes(array, value) {
  var length = array ? array.length : 0;//数组长度
  return !!length && baseIndexOf(array, value, 0) > -1;//调用baseIndexOf判断array是否包含value
}

/**
 * This function is like `arrayIncludes` except that it accepts a comparator.
 *
 * @private
 * @param {Array} [array] The array to inspect.
 * @param {*} target The value to search for.
 * @param {Function} comparator The comparator invoked per element.
 * @returns {boolean} Returns `true` if `target` is found, else `false`.
 */
//类似于数组的includes方法,区别是它的comparator需要作为参数传入
function arrayIncludesWith(array, value, comparator) {
  var index = -1,//循环索引
      length = array ? array.length : 0;//数组长度

  while (++index < length) {//循环数组,用comparator比较当前值和target,找到返回true,否则false
    if (comparator(value, array[index])) {
      return true;
    }
  }
  return false;
}

/**
 * A specialized version of `_.map` for arrays without support for iteratee
 * shorthands.
 *
 * @private
 * @param {Array} [array] The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns the new mapped array.
 */
//_.map基础实现
function arrayMap(array, iteratee) {
  var index = -1,//循环索引
      length = array ? array.length : 0,//数组长度
      result = Array(length);//结果数组

  while (++index < length) {//循环调用回调函数迭代器
    result[index] = iteratee(array[index], index, array);
  }
  return result;
}

/**
 * Appends the elements of `values` to `array`.
 *
 * @private
 * @param {Array} array The array to modify.
 * @param {Array} values The values to append.
 * @returns {Array} Returns `array`.
 */
//将values的元素添加到array的后面
function arrayPush(array, values) {
  var index = -1,//循环索引
      length = values.length,//添加的元素数组
      offset = array.length;//开始添加的偏移量,就是array的长度s

  while (++index < length) {//循环values数组,将其元素添加入array的后面
    array[offset + index] = values[index];
  }
  return array;
}

/**
 * A specialized version of `_.some` for arrays without support for iteratee
 * shorthands.
 *
 * @private
 * @param {Array} [array] The array to iterate over.
 * @param {Function} predicate The function invoked per iteration.
 * @returns {boolean} Returns `true` if any element passes the predicate check,
 *  else `false`.
 */
//类似于数组的some方法
function arraySome(array, predicate) {
  var index = -1,//循环索引
      length = array ? array.length : 0;//数组长度

  while (++index < length) {//循环调用传入的回调函数,参数是值,值的索引,数组本身
    if (predicate(array[index], index, array)) {
      return true;
    }
  }//如果有一个返回true,就返回true,否则,返回false
  return false;
}

/**
 * The base implementation of `_.findIndex` and `_.findLastIndex` without
 * support for iteratee shorthands.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {Function} predicate The function invoked per iteration.
 * @param {number} fromIndex The index to search from.
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
//找到数组中指定元素的位置
//array查找的数组,predicate每次循环调用的函数,fromIndex遍历的起始位置,fromRight是否反向查找
function baseFindIndex(array, predicate, fromIndex, fromRight) {
  var length = array.length,//数组长度
      index = fromIndex + (fromRight ? 1 : -1);//循环索引,如果反向循环就加1,如果正向就减1

  while ((fromRight ? index-- : ++index < length)) {
    if (predicate(array[index], index, array)) {//调用predicate,根据其返回的真假值,如果返回true说明找到了
      return index;
    }
  }
  return -1;//找不到返回-1
}

/**
 * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {*} value The value to search for.
 * @param {number} fromIndex The index to search from.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
//寻找数组中指定值的位置,找不到返回-1,fromIndex是查找起始位置
function baseIndexOf(array, value, fromIndex) {
  if (value !== value) {//如果value是NaN就调用baseFindIndex查找,传入的回调函数是自己实现的isNaN
    return baseFindIndex(array, baseIsNaN, fromIndex);
  }
  var index = fromIndex - 1,//循环索引
      length = array.length;//数组长度

  while (++index < length) {//如果当前值严格等于value,就返回index索引
    if (array[index] === value) {
      return index;
    }
  }
  return -1;//找不到返回-1
}

/**
 * The base implementation of `_.isNaN` without support for number objects.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
 */
//判断一个之是不是NaN
function baseIsNaN(value) {
  return value !== value;
}

/**
 * The base implementation of `_.property` without support for deep paths.
 *
 * @private
 * @param {string} key The key of the property to get.
 * @returns {Function} Returns the new accessor function.
 */
//返回一个方法,这个方法返回object的对应key的对应value
function baseProperty(key) {
  return function(object) {
    return object == null ? undefined : object[key];
  };
}

/**
 * The base implementation of `_.times` without support for iteratee shorthands
 * or max array length checks.
 *
 * @private
 * @param {number} n The number of times to invoke `iteratee`.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns the array of results.
 */
//_times的基础实现,返回一个长度为n的数组,元素值是遍历器处理过的index
//n是循环调用遍历器的次数,iteratee是遍历器
function baseTimes(n, iteratee) {
  var index = -1,//循环索引
      result = Array(n);//结果数组

  while (++index < n) {
    result[index] = iteratee(index);
  }
  return result;
}

/**
 * The base implementation of `_.unary` without support for storing metadata.
 *
 * @private
 * @param {Function} func The function to cap arguments for.
 * @returns {Function} Returns the new capped function.
 */
//_.unary的基础实现,创建一个函数只接受一个参数,忽略之后所有参数
function baseUnary(func) {
  return function(value) {
    return func(value);
  };
}

/**
 * Checks if a cache value for `key` exists.
 *
 * @private
 * @param {Object} cache The cache to query.
 * @param {string} key The key of the entry to check.
 * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
 */
//判断cache中是否有给定key
function cacheHas(cache, key) {
  return cache.has(key);
}

/**
 * Gets the value at `key` of `object`.
 *
 * @private
 * @param {Object} [object] The object to query.
 * @param {string} key The key of the property to get.
 * @returns {*} Returns the property value.
 */
function getValue(object, key) {
  return object == null ? undefined : object[key];
}

/**
 * Checks if `value` is a host object in IE < 9.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
 */
//检查一个值是否是宿主对象,在小于IE9的浏览器情况下,例如BOM和DOM对象
function isHostObject(value) {
  // Many host objects are `Object` objects that can coerce to strings
  // despite having improperly defined `toString` methods.
  //很多宿主对象可以强制转换成字符串经管它们的toString方法定义并不正确
  var result = false;
  if (value != null && typeof value.toString != 'function') {
    try {
      result = !!(value + '');
    } catch (e) {}
  }
  return result;
}

/**
 * Converts `map` to its key-value pairs.
 *
 * @private
 * @param {Object} map The map to convert.
 * @returns {Array} Returns the key-value pairs.
 */
//将自己实现的兼容式map数据类型转变成key-value形式
function mapToArray(map) {
  var index = -1,//循环索引
      result = Array(map.size);//结果数组

  map.forEach(function(value, key) {//循环赋值
    /*
    {key1: value1, key2: value2, ...}
    变成:
    [[key1, value1], [key2, value2], ...]
    */
    result[++index] = [key, value];
  });
  return result;
}

/**
 * Creates a unary function that invokes `func` with its argument transformed.
 *
 * @private
 * @param {Function} func The function to wrap.
 * @param {Function} transform The argument transform.
 * @returns {Function} Returns the new function.
 */
//创建一个一元方法,来处理函数的参数
function overArg(func, transform) {
  return function(arg) {
    return func(transform(arg));
  };
}

/**
 * Converts `set` to an array of its values.
 *
 * @private
 * @param {Object} set The set to convert.
 * @returns {Array} Returns the values.
 */
//将set类型转变成数组
function setToArray(set) {
  var index = -1,//循环索引
      result = Array(set.size);//结果数组

  set.forEach(function(value) {//循环赋值
    result[++index] = value;
  });
  return result;
}

/** Used for built-in method references. */
var arrayProto = Array.prototype,
    funcProto = Function.prototype,
    objectProto = Object.prototype;

/** Used to detect overreaching core-js shims. */
var coreJsData = root['__core-js_shared__'];

/** Used to detect methods masquerading as native. */
var maskSrcKey = (function() {
  var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
  return uid ? ('Symbol(src)_1.' + uid) : '';
}());

/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;

/**
 * Used to resolve the
 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
 * of values.
 */
var objectToString = objectProto.toString;

/** Used to detect if a method is native. */
var reIsNative = RegExp('^' +
  funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
  .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
);

/** Built-in value references. */
var Symbol = root.Symbol,
    Uint8Array = root.Uint8Array,
    propertyIsEnumerable = objectProto.propertyIsEnumerable,
    splice = arrayProto.splice,
    spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined;

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeKeys = overArg(Object.keys, Object),
    nativeMax = Math.max;

/* Built-in method references that are verified to be native. */
var DataView = getNative(root, 'DataView'),
    Map = getNative(root, 'Map'),
    Promise = getNative(root, 'Promise'),
    Set = getNative(root, 'Set'),
    WeakMap = getNative(root, 'WeakMap'),
    nativeCreate = getNative(Object, 'create');

/** Used to detect maps, sets, and weakmaps. */
var dataViewCtorString = toSource(DataView),
    mapCtorString = toSource(Map),
    promiseCtorString = toSource(Promise),
    setCtorString = toSource(Set),
    weakMapCtorString = toSource(WeakMap);

/** Used to convert symbols to primitives and strings. */
var symbolProto = Symbol ? Symbol.prototype : undefined,
    symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
    symbolToString = symbolProto ? symbolProto.toString : undefined;

/**
 * Creates a hash object.
 *
 * @private
 * @constructor
 * @param {Array} [entries] The key-value pairs to cache.
 */
//Hash类构造函数,接收参数是一个包含键值对数组的数组:
//[['key1', 'values1'], ['key2', 'values2']......]
function Hash(entries) {
  var index = -1,//循环索引
      length = entries ? entries.length : 0;//retries长度

  this.clear();//初始化
  while (++index < length) {//循环并设置数据到__data__属性里
    var entry = entries[index];
    this.set(entry[0], entry[1]);
  }
}

/**
 * Removes all key-value entries from the hash.
 *
 * @private
 * @name clear
 * @memberOf Hash
 */
//初始化Hash实例__data__属性
function hashClear() {
  this.__data__ = nativeCreate ? nativeCreate(null) : {};
  //Object.create({})
}

/**
 * Removes `key` and its value from the hash.
 *
 * @private
 * @name delete
 * @memberOf Hash
 * @param {Object} hash The hash to modify.
 * @param {string} key The key of the value to remove.
 * @returns {boolean} Returns `true` if the entry was removed, else `false`.
 */
//从Hash实例上删除指定key
function hashDelete(key) {
  return this.has(key) && delete this.__data__[key];
}

/**
 * Gets the hash value for `key`.
 *
 * @private
 * @name get
 * @memberOf Hash
 * @param {string} key The key of the value to get.
 * @returns {*} Returns the entry value.
 */
//获取Hash实例上指定key对应值
function hashGet(key) {
  var data = this.__data__;
  if (nativeCreate) {
    var result = data[key];
    return result === HASH_UNDEFINED ? undefined : result;
  }
  return hasOwnProperty.call(data, key) ? data[key] : undefined;
}

/**
 * Checks if a hash value for `key` exists.
 *
 * @private
 * @name has
 * @memberOf Hash
 * @param {string} key The key of the entry to check.
 * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
 */
//判断Hash实例上是否有指定key
function hashHas(key) {
  var data = this.__data__;
  return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
}

/**
 * Sets the hash `key` to `value`.
 *
 * @private
 * @name set
 * @memberOf Hash
 * @param {string} key The key of the value to set.
 * @param {*} value The value to set.
 * @returns {Object} Returns the hash instance.
 */
//Hash实例设置新值到__data__
function hashSet(key, value) {
  var data = this.__data__;
  data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
  return this;
}

// Add methods to `Hash`.
//以下都是Hash实例的方法
Hash.prototype.clear = hashClear;
Hash.prototype['delete'] = hashDelete;
Hash.prototype.get = hashGet;
Hash.prototype.has = hashHas;
Hash.prototype.set = hashSet;

/**
 * Creates an list cache object.
 *
 * @private
 * @constructor
 * @param {Array} [entries] The key-value pairs to cache.
 */
//ListCache构造函数,ListCache其实就是一个自己实现的Map数据类型
  //参数entries是键值对数组,结构如下
  /*
    [
      [key1, value1],
      [key2, value2],
      ...
    ]
  */
function ListCache(entries) {
  var index = -1,//循环索引
      length = entries ? entries.length : 0;//entries长度

  this.clear();//初始化实例上的__data__属性
  while (++index < length) {//循环参数,给__data__上存储键值对
    var entry = entries[index];
    this.set(entry[0], entry[1]);
  }
}

/**
 * Removes all key-value entries from the list cache.
 *
 * @private
 * @name clear
 * @memberOf ListCache
 */
//初始化ListCache实例__data__
function listCacheClear() {
  this.__data__ = [];
}

/**
 * Removes `key` and its value from the list cache.
 *
 * @private
 * @name delete
 * @memberOf ListCache
 * @param {string} key The key of the value to remove.
 * @returns {boolean} Returns `true` if the entry was removed, else `false`.
 */
//从ListCache实例上删除指定key
function listCacheDelete(key) {
  var data = this.__data__,
      index = assocIndexOf(data, key);

  if (index < 0) {
    return false;
  }
  var lastIndex = data.length - 1;
  if (index == lastIndex) {
    data.pop();
  } else {
    splice.call(data, index, 1);
  }
  return true;
}

/**
 * Gets the list cache value for `key`.
 *
 * @private
 * @name get
 * @memberOf ListCache
 * @param {string} key The key of the value to get.
 * @returns {*} Returns the entry value.
 */
//从ListCache实例上获取指定key对应值
function listCacheGet(key) {
  var data = this.__data__,
      index = assocIndexOf(data, key);

  return index < 0 ? undefined : data[index][1];
}

/**
 * Checks if a list cache value for `key` exists.
 *
 * @private
 * @name has
 * @memberOf ListCache
 * @param {string} key The key of the entry to check.
 * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
 */
//判断ListCache实例上是否存在指定key
function listCacheHas(key) {
  return assocIndexOf(this.__data__, key) > -1;
}

/**
 * Sets the list cache `key` to `value`.
 *
 * @private
 * @name set
 * @memberOf ListCache
 * @param {string} key The key of the value to set.
 * @param {*} value The value to set.
 * @returns {Object} Returns the list cache instance.
 */
//在ListCache实例上添加指定key
function listCacheSet(key, value) {
  var data = this.__data__,
      index = assocIndexOf(data, key);//在__data__上寻找有没有重复的key,如果没有就返回-1,如果有就返回索引

  if (index < 0) {//如果没有重复的key,__data__存入键值对
    data.push([key, value]);
  } else {//如果有重复的key,就改变对应的value
    data[index][1] = value;
  }
  return this;
}

//以下都是ListCache实例方法
// Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear;
ListCache.prototype['delete'] = listCacheDelete;
ListCache.prototype.get = listCacheGet;
ListCache.prototype.has = listCacheHas;
ListCache.prototype.set = listCacheSet;

/**
 * Creates a map cache object to store key-value pairs.
 *
 * @private
 * @constructor
 * @param {Array} [entries] The key-value pairs to cache.
 */
//MapCache类型构造函数,创建一个map缓存对象来储存键值对
//entries参数是键值对数组
function MapCache(entries) {
  var index = -1,//循环参数
      length = entries ? entries.length : 0;//entries长度

  this.clear();//初始化实例上的size属性和__data__属性
  while (++index < length) {//循环键值对数组,并调用this.prototype.set方法
    var entry = entries[index];
    this.set(entry[0], entry[1]);
  }
}

/*
  SetCacheObject
  {
    __data__: {
      size: xxx,
      __data__: {
        hash: new Hash,
        map: new Map,
        string: new Hash
      }
    }
  }

  =>
  {
    __data__: {
      size: xxx,
      __data__: {
        hash: {
          size: xxx,
          __data__: {
            values4: '__lodash_hash_undefined__',
            values6: '__lodash_hash_undefined__',
            ...
          }
        },
        map: new Map,
        string: {
          size: xxx,
          __data__: {
            values3: '__lodash_hash_undefined__',
            values8: '__lodash_hash_undefined__',
            ...
          }
        }
      }
    }
  }
*/

/**
 * Removes all key-value entries from the map.
 *
 * @private
 * @name clear
 * @memberOf MapCache
 */
//清除当前MapCache实例上所有键值对
function mapCacheClear() {
  this.__data__ = {
    'hash': new Hash,
    'map': new (Map || ListCache),
    'string': new Hash
  };
}

/**
 * Removes `key` and its value from the map.
 *
 * @private
 * @name delete
 * @memberOf MapCache
 * @param {string} key The key of the value to remove.
 * @returns {boolean} Returns `true` if the entry was removed, else `false`.
 */
//从MapCache实例上移除指定key
function mapCacheDelete(key) {
  return getMapData(this, key)['delete'](key);
}

/**
 * Gets the map value for `key`.
 *
 * @private
 * @name get
 * @memberOf MapCache
 * @param {string} key The key of the value to get.
 * @returns {*} Returns the entry value.
 */
//从MapCache实例上获取指定key的值
function mapCacheGet(key) {
  return getMapData(this, key).get(key);
}

/**
 * Checks if a map value for `key` exists.
 *
 * @private
 * @name has
 * @memberOf MapCache
 * @param {string} key The key of the entry to check.
 * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
 */
//判断MapCache实例上是否存在指定key
function mapCacheHas(key) {
  return getMapData(this, key).has(key);
}

/**
 * Sets the map `key` to `value`.
 *
 * @private
 * @name set
 * @memberOf MapCache
 * @param {string} key The key of the value to set.
 * @param {*} value The value to set.
 * @returns {Object} Returns the map cache instance.
 */
function mapCacheSet(key, value) {
  //根据key的数据类型获取map里对应的数据集合,要么是Map对象,要么是ListCache对象,要么是Hash对象
  //调用数据集合对象上的set方法,设置键值对到上面
  getMapData(this, key).set(key, value);
  return this;
}

// Add methods to `MapCache`.
MapCache.prototype.clear = mapCacheClear;//MapCache原型上的clear方法
MapCache.prototype['delete'] = mapCacheDelete;//MapCache原型上的delete方法
MapCache.prototype.get = mapCacheGet;//MapCache原型上的get方法
MapCache.prototype.has = mapCacheHas;//MapCache原型上的has方法
MapCache.prototype.set = mapCacheSet;//MapCache原型上的set方法

/**
 *
 * Creates an array cache object to store unique values.
 *
 * @private
 * @constructor
 * @param {Array} [values] The values to cache.
 */
//SetCachel构造函数,为一个数组创建缓存加快查找速度
function SetCache(values) {//values参数为一个数组,将为它设置缓存
  var index = -1,//循环变量
      length = values ? values.length : 0;//values数组长度

  this.__data__ = new MapCache;//为SetCache新实例添加__data__属性,__data__里存放一个MapCache实例
  while (++index < length) {
    this.add(values[index]);//根据每个值调用this.prototype.add方法
  }
}

/*
  SetCache类型

  values
  [value1, value2......]

  new SetCache(values)

  SetCacheObject
  {
    __data__: {
      value1: '__lodash_hash_undefined__',
      value2: '__lodash_hash_undefined__',
      ......
    }
  }
*/

/**
 * Adds `value` to the array cache.
 *
 * @private
 * @name add
 * @memberOf SetCache
 * @alias push
 * @param {*} value The value to cache.
 * @returns {Object} Returns the cache instance.
 */
//this.prototype.add,调用当前SetCache实例上的MapCache实例的set方法,将值添加入缓存里
  //MapCache上设置的值类型是键值对,这里调用,value是key,HASH_UNDEFINED是value
function setCacheAdd(value) {
  this.__data__.set(value, HASH_UNDEFINED);
  return this;
}

/**
 * Checks if `value` is in the array cache.
 *
 * @private
 * @name has
 * @memberOf SetCache
 * @param {*} value The value to search for.
 * @returns {number} Returns `true` if `value` is found, else `false`.
 */
//this.prototype.has,调用当前SetCache实例上的MapCache实例的has方法,判断当前缓存里有没有这个值
function setCacheHas(value) {
  return this.__data__.has(value);
}

// Add methods to `SetCache`.
SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;//为SetCache原型添加add方法
SetCache.prototype.has = setCacheHas;//为SetCache原型添加has方法

/**
 * Creates a stack cache object to store key-value pairs.
 *
 * @private
 * @constructor
 * @param {Array} [entries] The key-value pairs to cache.
 */
function Stack(entries) {
  this.__data__ = new ListCache(entries);
}

/**
 * Removes all key-value entries from the stack.
 *
 * @private
 * @name clear
 * @memberOf Stack
 */
function stackClear() {
  this.__data__ = new ListCache;
}

/**
 * Removes `key` and its value from the stack.
 *
 * @private
 * @name delete
 * @memberOf Stack
 * @param {string} key The key of the value to remove.
 * @returns {boolean} Returns `true` if the entry was removed, else `false`.
 */
function stackDelete(key) {
  return this.__data__['delete'](key);
}

/**
 * Gets the stack value for `key`.
 *
 * @private
 * @name get
 * @memberOf Stack
 * @param {string} key The key of the value to get.
 * @returns {*} Returns the entry value.
 */
function stackGet(key) {
  return this.__data__.get(key);
}

/**
 * Checks if a stack value for `key` exists.
 *
 * @private
 * @name has
 * @memberOf Stack
 * @param {string} key The key of the entry to check.
 * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
 */
function stackHas(key) {
  return this.__data__.has(key);
}

/**
 * Sets the stack `key` to `value`.
 *
 * @private
 * @name set
 * @memberOf Stack
 * @param {string} key The key of the value to set.
 * @param {*} value The value to set.
 * @returns {Object} Returns the stack cache instance.
 */
function stackSet(key, value) {
  var cache = this.__data__;
  if (cache instanceof ListCache) {
    var pairs = cache.__data__;
    if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {
      pairs.push([key, value]);
      return this;
    }
    cache = this.__data__ = new MapCache(pairs);
  }
  cache.set(key, value);
  return this;
}

// Add methods to `Stack`.
Stack.prototype.clear = stackClear;
Stack.prototype['delete'] = stackDelete;
Stack.prototype.get = stackGet;
Stack.prototype.has = stackHas;
Stack.prototype.set = stackSet;

/**
 * Creates an array of the enumerable property names of the array-like `value`.
 *
 * @private
 * @param {*} value The value to query.
 * @param {boolean} inherited Specify returning inherited property names.
 * @returns {Array} Returns the array of property names.
 */
//创建一个包含一个array-like对象的所有自身可枚举属性的key的数组
function arrayLikeKeys(value, inherited) {
  // Safari 8.1 makes `arguments.callee` enumerable in strict mode.
  // Safari 9 makes `arguments.length` enumerable in strict mode.
  var result = (isArray(value) || isArguments(value))
    ? baseTimes(value.length, String)
    : [];
    //结果数组,如果value是array或者arguments对象,调用baseTimes返回一个结果数组,元素是String化的索引值

  var length = result.length,//结果数组长度
      skipIndexes = !!length;

  for (var key in value) {//循环value对象
    if ((inherited || hasOwnProperty.call(value, key)) &&
        !(skipIndexes && (key == 'length' || isIndex(key, length)))) {
          //如果除了索引属性和length属性之外还有其他属性,加入结果数组
      result.push(key);
    }
  }
  return result;
}

/**
 * Gets the index at which the `key` is found in `array` of key-value pairs.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {*} key The key to search for.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
//寻找键值对数组中key对应的数据索引
function assocIndexOf(array, key) {
  var length = array.length;//数组长度
  while (length--) {//循环寻找键值对对应的索引,找到就返回,没找到就返回-1
    if (eq(array[length][0], key)) {
      return length;
    }
  }
  return -1;
}

/**
 * The base implementation of methods like `_.difference` without support
 * for excluding multiple arrays or iteratee shorthands.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {Array} values The values to exclude.
 * @param {Function} [iteratee] The iteratee invoked per element.
 * @param {Function} [comparator] The comparator invoked per element.
 * @returns {Array} Returns the new array of filtered values.
 */
//_.difference基础实现
//array,用于检查的数组
//values,用于排除元素的数组
//iteratee,迭代器,类型是function,循环会调用
//comparator,比较器,类型function,循环会调用
function baseDifference(array, values, iteratee, comparator) {
  var index = -1,//循环索引
      includes = arrayIncludes,//arrayIncludes方法,判断数组是否包含给定值,类似原生的includes
      isCommon = true,//isCommon用来标记是否要使用普通的比较方式
      length = array.length,//数组长度
      result = [],//结果数组
      valuesLength = values.length;//用于排除的数组的长度

  if (!length) {//如果array无长度,返回空数组
    return result;
  }
  if (iteratee) {//如果传递了迭代器参数,就循环values,对每个值运行迭代器,生成新的values
    values = arrayMap(values, baseUnary(iteratee));
  }
  if (comparator) {//如果传递了比较器参数,就是用arrayIncludesWith方法
    includes = arrayIncludesWith;
    isCommon = false;
  }
  else if (values.length >= LARGE_ARRAY_SIZE) {
    //如果没有传递自定义includes方法,且values数组的长度超过200,indludes方法就换成cacheHas,启用cache以达到性能优化的效果
    includes = cacheHas;
    isCommon = false;
    values = new SetCache(values);
  }
  outer:
  //label语句,将下面的for循环标记,continue会跳过本次循环,继续走下一次外层循环
  while (++index < length) {//循环array
    var value = array[index],//array当前值
        computed = iteratee ? iteratee(value) : value;
        //如果有iteratee参数,就把array数组的循环当前值value传入iteratee,返回值存为computed

    value = (comparator || value !== 0) ? value : 0;
    if (isCommon && computed === computed) {
      //排除array当前循环值是否是NaN的情况,isCommon用来标记是否要使用普通的比较方式
      var valuesIndex = valuesLength;//values数组循环索引
      while (valuesIndex--) {//倒序循环values数组
        if (values[valuesIndex] === computed) {
          //循环values,如果发现values里有值和当前array循环元素相等,直接跳出values循环,循环下一次array
          continue outer;
        }
      }
      result.push(value);//values循环结束没有发现有相等的值,就push入结果数组
    }
    else if (!includes(values, computed, comparator)) {
      //如果当前array循环值是NaN或者需要使用特殊比较方法
      //调用includes判断values中有没有和当前array循环值相等的
      result.push(value);
    }
  }
  return result;
}

/**
 * The base implementation of `_.flatten` with support for restricting flattening.
 *
 * @private
 * @param {Array} array The array to flatten.
 * @param {number} depth The maximum recursion depth.
 * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
 * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
 * @param {Array} [result=[]] The initial result value.
 * @returns {Array} Returns the new flattened array.
 */
//这个方法用于数组展开
//array需要展开操作的数组,depth需要展开的层数
//predicate用于判断值是否可展开
//isStrict标识用于判断是否约束值必须通过predicate方法的检查
function baseFlatten(array, depth, predicate, isStrict, result) {
  var index = -1,//循环索引
      length = array.length;//数组长度

  predicate || (predicate = isFlattenable);//predicate每次循环都会调用,用来判断当前值是否是一个可展开的
  result || (result = []);

  while (++index < length) {
    var value = array[index];//当前循环的元素值
    if (depth > 0 && predicate(value)) {//如果展开层数大于0且当前循环值可展开
      if (depth > 1) {//如果展开层数大于一层就继续递归调用,层数减一
        // Recursively flatten arrays (susceptible to call stack limits).
        baseFlatten(value, depth - 1, predicate, isStrict, result);
      } else {//如果只展开一层,就展开后push到result里
        arrayPush(result, value);
      }
    } else if (!isStrict) {//如果没有传递isStrict标识,就直接将当前循环值push入结果数组
      result[result.length] = value;
    }
  }
  return result;
}

/**
 * The base implementation of `_.get` without support for default values.
 *
 * @private
 * @param {Object} object The object to query.
 * @param {Array|string} path The path of the property to get.
 * @returns {*} Returns the resolved value.
 */
//根据path获取object上的属性值
//object需要获取属性值的对象
//path属性所在的路径
function baseGet(object, path) {
  path = isKey(path, object) ? [path] : castPath(path);
  //判断path是不是合法属性名,如果是就存成[path],如果不是就调用castPath方法生成path的数组

  var index = 0,//循环索引
      length = path.length;//path数字的长度

  while (object != null && index < length) {//获取深层的值
    object = object[toKey(path[index++])];
  }
  return (index && index == length) ? object : undefined;//找到就返回,没找到返回undefined
}

/**
 * The base implementation of `getTag`.
 *
 * @private
 * @param {*} value The value to query.
 * @returns {string} Returns the `toStringTag`.
 */
//调用Object.prototype.toString获得数据的类型tag
function baseGetTag(value) {
  return objectToString.call(value);
}

/**
 * The base implementation of `_.hasIn` without support for deep paths.
 *
 * @private
 * @param {Object} [object] The object to query.
 * @param {Array|string} key The key to check.
 * @returns {boolean} Returns `true` if `key` exists, else `false`.
 */
//object是否存在path路径的浅层实现,不考虑深层path路径
function baseHasIn(object, key) {//直接用in操作符判断
  return object != null && key in Object(object);
}

/**
 * The base implementation of `_.isEqual` which supports partial comparisons
 * and tracks traversed objects.
 *
 * @private
 * @param {*} value The value to compare.
 * @param {*} other The other value to compare.
 * @param {Function} [customizer] The function to customize comparisons.
 * @param {boolean} [bitmask] The bitmask of comparison flags.
 *  The bitmask may be composed of the following flags:
 *     1 - Unordered comparison
 *     2 - Partial comparison
 * @param {Object} [stack] Tracks traversed `value` and `other` objects.
 * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
 */
//_.isEqual的基础实现,深度比较两个对象是否是相等的
//value要比较的对象,other要比较的另一个对象,customizer自定义比较方法
//bitmask为1说明是无序的比较,为2说明是部分比较
//stack追踪传入的参数
function baseIsEqual(value, other, customizer, bitmask, stack) {
  if (value === other) {//如果value和other严格相等,直接返回true
    return true;
  }
  if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
    //判断有值为空或者NaN,反正不是数组或对象的情况的情况
    return value !== value && other !== other;
  }
  return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack);
  //调用baseIsEqualDeep来判断
}

/**
 * A specialized version of `baseIsEqual` for arrays and objects which performs
 * deep comparisons and tracks traversed objects enabling objects with circular
 * references to be compared.
 *
 * @private
 * @param {Object} object The object to compare.
 * @param {Object} other The other object to compare.
 * @param {Function} equalFunc The function to determine equivalents of values.
 * @param {Function} [customizer] The function to customize comparisons.
 * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual`
 *  for more details.
 * @param {Object} [stack] Tracks traversed `object` and `other` objects.
 * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
 */
//深度比较数组和对象是否相等
function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) {
  var objIsArr = isArray(object),//判断object是不是数组
      othIsArr = isArray(other),//判断other是不是数组
      objTag = arrayTag,//'[object Array]'
      othTag = arrayTag;//'[object Array]'

  if (!objIsArr) {
    objTag = getTag(object);//如果object不是数组,就获取object的数据类型tag
    objTag = objTag == argsTag ? objectTag : objTag;
  }
  if (!othIsArr) {
    othTag = getTag(other);//如果other不是数组,就获取other的数据类型tag
    othTag = othTag == argsTag ? objectTag : othTag;
  }
  var objIsObj = objTag == objectTag && !isHostObject(object),//object是对象类型且不是宿主对象
      othIsObj = othTag == objectTag && !isHostObject(other),//other是对象类型且不是宿主对象
      isSameTag = objTag == othTag;//object和other的类型tag一样

  if (isSameTag && !objIsObj) {//object和other的tag相等且它们不是本地对象类型
    stack || (stack = new Stack);//新建一个ListCache类型数据,ListCache其实就是一个自己实现的Map数据类型
    return (objIsArr || isTypedArray(object))
      ? equalArrays(object, other, equalFunc, customizer, bitmask, stack)
      : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack);
      //如果都是数组类型或者typedArray类型就调用equalArrays方法判断
      //否则调用equalByTag判断
  }
  if (!(bitmask & PARTIAL_COMPARE_FLAG)) {//如果不是部分比较,且是object类型
    //此处__warpped__属性看不明白???
    var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
        othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');

    if (objIsWrapped || othIsWrapped) {
      var objUnwrapped = objIsWrapped ? object.value() : object,
          othUnwrapped = othIsWrapped ? other.value() : other;

      stack || (stack = new Stack);
      return equalFunc(objUnwrapped, othUnwrapped, customizer, bitmask, stack);//递归调用baseIsEqual
    }
  }
  if (!isSameTag) {//如果toStringTag不一样,直接返回false
    return false;
  }
  stack || (stack = new Stack);
  return equalObjects(object, other, equalFunc, customizer, bitmask, stack);
  //其他情况使用equalObjects来判断
}

/**
 * The base implementation of `_.isMatch` without support for iteratee shorthands.
 *
 * @private
 * @param {Object} object The object to inspect.
 * @param {Object} source The object of property values to match.
 * @param {Array} matchData The property names, values, and compare flags to match.
 * @param {Function} [customizer] The function to customize comparisons.
 * @returns {boolean} Returns `true` if `object` is a match, else `false`.
 */
//object,给定对象
//source,用来比较的源对象
//matchData,source参数的[key, value, boolean]的数组的形式,第三个布尔值表明当前值是否适合用===比较
//自定义比较函数
function baseIsMatch(object, source, matchData, customizer) {
  var index = matchData.length,//循环索引,从结尾开始循环
      length = index,//source长度
      noCustomizer = !customizer;//有没有传递自定义比较方法的标识

  if (object == null) {//如果object为空,若source有属性,返回false,若source无属性,返回true
    return !length;
  }
  object = Object(object);//强制转换object
  while (index--) {//按source属性长度循环
    var data = matchData[index];//当前matchData
    if ((noCustomizer && data[2])
          ? data[1] !== object[data[0]]
          : !(data[0] in object)
        ) {//如果没有自定义比较方法且当前source属性值适合===判断,也就是说是简单类型数据,就直接用!==判断是否不相等
          //否则用in来判断object是否不含有source key属性
      return false;
    }
  }
  //上面循环先循环一遍判断适合使用===直接判断的属性,下面循环再循环一遍判断其他需要深度循环判断的属性
  //上面循环结束后index循环索引变成0,现在再次正向循环
  while (++index < length) {
    data = matchData[index];//当前matchData
    var key = data[0],//当前source key
        objValue = object[key],//当前object 对应source key 的值
        srcValue = data[1];//当前source value

    if (noCustomizer && data[2]) {//如果没有自定义比较方法,且是简单类型,就判断objValue上是否不存在此属性
      //如果不存在直接返回false
      if (objValue === undefined && !(key in object)) {
        return false;
      }
    } else {
      var stack = new Stack;//新实例化一个Stack对象
      if (customizer) {//如果有自定义比较方法,就用自定义的比较
        var result = customizer(objValue, srcValue, key, object, source, stack);
      }
      if (!(result === undefined
            ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stack)
            : result
          )) {//如果没有自定义比较方法,就用baseIsEqual来比较
        return false;
      }
    }
  }
  return true;
}

/**
 * The base implementation of `_.isNative` without bad shim checks.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a native function,
 *  else `false`.
 */
function baseIsNative(value) {
  if (!isObject(value) || isMasked(value)) {
    return false;
  }
  var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;
  return pattern.test(toSource(value));
}

/**
 * The base implementation of `_.isTypedArray` without Node.js optimizations.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
 */
//_.isTypedArray基础实现,不使用nodejs优化的方法
function baseIsTypedArray(value) {
  return isObjectLike(value) &&
    isLength(value.length) && !!typedArrayTags[objectToString.call(value)];
}

/**
 * The base implementation of `_.iteratee`.
 *
 * @private
 * @param {*} [value=_.identity] The value to convert to an iteratee.
 * @returns {Function} Returns the iteratee.
 */
//_.iteratee的基础实现,把一个值转换成一个迭代器函数
function baseIteratee(value) {
  // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
  // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
  if (typeof value == 'function') {//如果value本来就是function,直接返回
    return value;
  }
  if (value == null) {//如果value是空,迭代器就使用事先定义好的identity方法
    //identity方法返回原值,不做操作
    return identity;
  }
  if (typeof value == 'object') {//如果value是对象
    return isArray(value)
      ? baseMatchesProperty(value[0], value[1])
      : baseMatches(value);
      //如果value是一个数组,调用baseMatchesProperty,则value[0]是path,获取对象上的属性的路径;value[1]是srcValue,和之后传入参数根据路径获取到的属性值比较用的值,baseMatchesProperty会返回一个方法,这个方法判断value[1]是否和传入的对象根据value[0]路径获取到的属性 是相等的,内部比较使用类似baseIsEqual的深层比较方法。相等返回true,否则false
      //如果value的类型不是一个数组,是一个对象,那么调用baseMatches,baseMatches创建一个方法,这个方法会使用部分的(partial)深度比较来比较给定的对象和source对象,如果给定对象拥有相等的属性值,就返回true,否则false。这里给baseMatches传入的value参数就是source,之后differenctBy循环的时候所有值都会和source比较
  }
  return property(value);
  //如果value都不是以上情况,那就调用property,创建一个方法,这个方法可以根据给定path属性路径返回给定对象的属性值
}

/**
 * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
 *
 * @private
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 */
//_.keys的基础实现,将对象的可枚举属性的key组成一个数组返回
function baseKeys(object) {
  if (!isPrototype(object)) {//如果不是prototype对象,使用原生Object.keys方法
    return nativeKeys(object);
  }
  var result = [];//结果数组
  for (var key in Object(object)) {
    if (hasOwnProperty.call(object, key) && key != 'constructor') {
      //不等于constructor的自身属性push入结果数组
      result.push(key);
    }
  }
  return result;
}

/**
 * The base implementation of `_.matches` which doesn't clone `source`.
 *
 * @private
 * @param {Object} source The object of property values to match.
 * @returns {Function} Returns the new spec function.
 */
//_.matches方法的基础实现
//创建一个方法,这个方法会使用部分的(partial)深度比较来比较给定的对象和source对象,如果给定对象拥有相等的属性值,就返回true,否则false
function baseMatches(source) {
  var matchData = getMatchData(source);
  //getMatchData,把对象变成[key, value, boolean]的数组的形式,第三个布尔值表明当前值是否适合用===比较
  if (matchData.length == 1 && matchData[0][2]) {//如果source对象只有一个属性
    return matchesStrictComparable(matchData[0][0], matchData[0][1]);
    //matchesStrictComparable返回一个方法,这个方法用于比较传入对象object key值对应的值是否和给定值srcValue相等
    //matchData[0][0]就是key,matchData[0][1]就是srcValue
  }
  return function(object) {
    //如果source有多个属性,那么就调用baseIsMatch
    return object === source || baseIsMatch(object, source, matchData);
  };
}

/**
 * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
 *
 * @private
 * @param {string} path The path of the property to get.
 * @param {*} srcValue The value to match.
 * @returns {Function} Returns the new spec function.
 */
//生成一个函数,这个函数可以深度比较对象的某个属性是否与给定值相等
//path参数是想要获取的属性的路径
//srcValue是用来匹配的值
function baseMatchesProperty(path, srcValue) {
  if (isKey(path) && isStrictComparable(srcValue)) {
    //如果path是属性名 并且 srcValue可以适用严格等于===来比较
    return matchesStrictComparable(toKey(path), srcValue);
    //返回一个返回object[key] === srcValue判断结果的函数
  }
  return function(object) {
    var objValue = get(object, path);//根据path获取object上对应健的值
    return (objValue === undefined && objValue === srcValue)
      ? hasIn(object, path)
      : baseIsEqual(srcValue, objValue, undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG);
      //返回布尔值,判断根据path获取到的值和给定值是否相等
      //如果获取到的值和用来比较的值都是undefined,那就object中是否存在这个path路径
      //hasIn判断path路径在给定object中是否存在
      //否则用baseIsEqual判断
  };
}

/**
 * A specialized version of `baseProperty` which supports deep paths.
 *
 * @private
 * @param {Array|string} path The path of the property to get.
 * @returns {Function} Returns the new accessor function.
 */
//返回一个方法,这个方法根据path返回object的对应属性值
function basePropertyDeep(path) {
  return function(object) {
    return baseGet(object, path);
  };
}

/**
 * The base implementation of `_.rest` which doesn't validate or coerce arguments.
 *
 * @private
 * @param {Function} func The function to apply a rest parameter to.
 * @param {number} [start=func.length-1] The start position of the rest parameter.
 * @returns {Function} Returns the new function.
 */
//_.rest方法的基础实现,为函数实现ES6的rest参数
//rest参数的起始位置,默认是形参个数减去1,func.length - 1
function baseRest(func, start) {
  start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
  //处理rest参数的起始位置,如果没传递start,就用函数形参个数减一,如果传递了,就在start和0之间选一个最大值
  return function() {
    var args = arguments,//函数接收到的参数数组
        index = -1,//循环索引
        length = nativeMax(args.length - start, 0),//rest参数的长度
        array = Array(length);//创建一个长度为rest参数长度的数组

    while (++index < length) {//循环rest参数长度,将rest参数全部存入array变量中
      array[index] = args[start + index];
    }
    index = -1;//循环参数
    var otherArgs = Array(start + 1);//创建一个数组存放除了rest参数以外的其他参数
    while (++index < start) {//循环获取其他参数存入otherArgs中
      otherArgs[index] = args[index];
    }
    otherArgs[start] = array;//otherArgs的最后一个参数就是rest参数数组
    return apply(func, this, otherArgs);//调用func,参数已处理成带有rest参数的形式
  };
}

/**
 * The base implementation of `_.toString` which doesn't convert nullish
 * values to empty strings.
 *
 * @private
 * @param {*} value The value to process.
 * @returns {string} Returns the string.
 */
//转换一个值为字符串,不会处理空值
function baseToString(value) {
  // Exit early for strings to avoid a performance hit in some environments.
  if (typeof value == 'string') {//如果已经是字符串,直接返回
    return value;
  }
  if (isSymbol(value)) {//如果是Symbol对象,调用Symbol.prototype.toString处理
    return symbolToString ? symbolToString.call(value) : '';
  }
  var result = (value + '');//隐式转换
  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;//处理-0的情况
}

/**
 * Casts `value` to a path array if it's not one.
 *
 * @private
 * @param {*} value The value to inspect.
 * @returns {Array} Returns the cast property path array.
 */
//把一个.分隔的path值转换成一个path数组
function castPath(value) {
  return isArray(value) ? value : stringToPath(value);
  //如果path值已经是数组了,直接返回;如果不是调用stringToPath方法转换
}

/**
 * A specialized version of `baseIsEqualDeep` for arrays with support for
 * partial deep comparisons.
 *
 * @private
 * @param {Array} array The array to compare.
 * @param {Array} other The other array to compare.
 * @param {Function} equalFunc The function to determine equivalents of values.
 * @param {Function} customizer The function to customize comparisons.
 * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
 *  for more details.
 * @param {Object} stack Tracks traversed `array` and `other` objects.
 * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
 */
//深度比较数组是否相等
//partical部分比较的意思就是,other的长度可以比array长,other在包含array所有元素的情况下,还可以有自己的独特于array的元素
function equalArrays(array, other, equalFunc, customizer, bitmask, stack) {
  //bitmask内部使用的时候值只能是1或2,1代表无序比较,2代表部分比较
  //bitmask按位与后,只有按位与操作符两边数字相等的情况下才会返回整数,否则返回0
  var isPartial = bitmask & PARTIAL_COMPARE_FLAG,//是否部分比较
      arrLength = array.length,//array长度
      othLength = other.length;//other长度

  if (arrLength != othLength && !(isPartial && othLength > arrLength)) {//长度不符合要求,直接返回false
    return false;
  }
  // Assume cyclic values are equal.
  //一开始先假设循环值都是相等的
  //stack默认是listCache对象,是自己实现的Map数据类型
  var stacked = stack.get(array);//获取stack上的array key对应的value
  if (stacked && stack.get(other)) {//如果stack key存在,就和other key比较
    return stacked == other;
  }
  var index = -1,//循环索引
      result = true,//一开始先假设循环值都是相等的
      //如果是无序比较,就实例化一个SetCache对象
      seen = (bitmask & UNORDERED_COMPARE_FLAG) ? new SetCache : undefined;

  stack.set(array, other);//给stack里存上array和other,交叉存
  stack.set(other, array);

  // Ignore non-index properties.
  while (++index < arrLength) {//循环array的长度
    var arrValue = array[index],//当前循环的array值
        othValue = other[index];//当前循环的other值

    if (customizer) {//如果提供了customizer比较方法,就用它来比较,partial部分比较要反过来传递array和other
      var compared = isPartial
        ? customizer(othValue, arrValue, index, other, array, stack)
        : customizer(arrValue, othValue, index, array, other, stack);
    }
    if (compared !== undefined) {//如果自定义比较有了结果,且为真,就continue继续下一次循环,否则result=false,跳出循环
      if (compared) {
        continue;
      }
      result = false;
      break;
    }
    // Recursively compare arrays (susceptible to call stack limits).
    //递归地比较数组元素值是否相等
    if (seen) {//如果创建了seen变量,说明是无序比较
      //seen是SetCache对象,也是利用key-value形式存储值
      //遍历other,如果有值和当前array的值相等,就存入seen中,并且arraySome返回true,否则返回false跳出循环
      if (!arraySome(other, function(othValue, othIndex) {
            if (!seen.has(othIndex) &&
                (arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) {
              return seen.add(othIndex);
            }
          })) {
        result = false;
        break;
      }
    } else if (!(
          arrValue === othValue ||
            equalFunc(arrValue, othValue, customizer, bitmask, stack)
        )) {//其他情况比较
      result = false;
      break;
    }
  }
  stack['delete'](array);//stack上删除array和other属性
  stack['delete'](other);
  return result;
}

/**
 * A specialized version of `baseIsEqualDeep` for comparing objects of
 * the same `toStringTag`.
 *
 * **Note:** This function only supports comparing values with tags of
 * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
 *
 * @private
 * @param {Object} object The object to compare.
 * @param {Object} other The other object to compare.
 * @param {string} tag The `toStringTag` of the objects to compare.
 * @param {Function} equalFunc The function to determine equivalents of values.
 * @param {Function} customizer The function to customize comparisons.
 * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
 *  for more details.
 * @param {Object} stack Tracks traversed `object` and `other` objects.
 * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
 */
//比较toStringTag相等的两个对象是否相等
//object用来比较的对象
//other用来比较的另一个对象
//tag比较对象的toStringTag
//equalFunc基础比较相等方法,用来递归调用
//customizer自定义比较方法
//bitmask是否无序比较或者部分比较的标志
//stack,key-value结构跟踪比较的对象
function equalByTag(object, other, tag, equalFunc, customizer, bitmask, stack) {
  switch (tag) {//根据toStringTag来比较object和other对象
    case dataViewTag:
    //'[object DataView]'
      if ((object.byteLength != other.byteLength) ||
          (object.byteOffset != other.byteOffset)) {
        return false;
      }
      object = object.buffer;
      other = other.buffer;

    case arrayBufferTag:
    //'[object ArrayBuffer]'
      if ((object.byteLength != other.byteLength) ||
          !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
        return false;
      }
      return true;

    case boolTag:
    //'[object Boolean]'
    case dateTag:
    //'[object Date]'
    case numberTag:
    //'[object Number]'
      // Coerce booleans to `1` or `0` and dates to milliseconds.
      // Invalid dates are coerced to `NaN`.
      //强制转换布尔值到1或0,时间对象转换为毫秒,无效的时间转换为NaN
      return eq(+object, +other);

    case errorTag:
    //'[object Error]'
      return object.name == other.name && object.message == other.message;

    case regexpTag:
    //'[object RegExp]'
    case stringTag:
    //'[object String]'
      // Coerce regexes to strings and treat strings, primitives and objects,
      // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
      // for more details.
      return object == (other + '');

    case mapTag:
    //'[object Map]'
      var convert = mapToArray;
      //将map转换成数组的方法
      /*
    {key1: value1, key2: value2, ...}
    变成:
    [[key1, value1], [key2, value2], ...]
    */

    case setTag:
    //'[object Set]'
      var isPartial = bitmask & PARTIAL_COMPARE_FLAG;//是否部分比较
      convert || (convert = setToArray);//将map或者set转换成数组的方法,mapToArray或者setToArray

      if (object.size != other.size && !isPartial) {//如果不是部分比较,且长度不一样,直接返回false
        return false;
      }
      // Assume cyclic values are equal.
      //假设循环值都相等,从中判断不相等的情况
      var stacked = stack.get(object);
      if (stacked) {
        return stacked == other;
      }
      bitmask |= UNORDERED_COMPARE_FLAG;

      // Recursively compare objects (susceptible to call stack limits).
      stack.set(object, other);//在stack里把object和other都存上
      var result = equalArrays(convert(object), convert(other), equalFunc, customizer, bitmask, stack);
      //把object和other都转换成数组,然后用equalArrays方法来比较是否相等
      stack['delete'](object);//清除stack
      return result;

    case symbolTag:
    //'[object Symbol]'
      if (symbolValueOf) {//Symbol.prototype.valueOf
        return symbolValueOf.call(object) == symbolValueOf.call(other);
      }
  }
  return false;
}

/**
 * A specialized version of `baseIsEqualDeep` for objects with support for
 * partial deep comparisons.
 *
 * @private
 * @param {Object} object The object to compare.
 * @param {Object} other The other object to compare.
 * @param {Function} equalFunc The function to determine equivalents of values.
 * @param {Function} customizer The function to customize comparisons.
 * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
 *  for more details.
 * @param {Object} stack Tracks traversed `object` and `other` objects.
 * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
 */
//判断object类型深度比较是否相等,支持部分比较
function equalObjects(object, other, equalFunc, customizer, bitmask, stack) {
  var isPartial = bitmask & PARTIAL_COMPARE_FLAG,//是否部分比较
      objProps = keys(object),//Object.keys(),返回key组成的数组
      objLength = objProps.length,
      othProps = keys(other),//Object.keys(),返回key组成的数组
      othLength = othProps.length;

  if (objLength != othLength && !isPartial) {//如果不是部分比较,且object和other的key长度不一样,返回false
    return false;
  }
  var index = objLength;//循环索引是object key数组的长度
  while (index--) {
    var key = objProps[index];//object 的key
    if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
      //如果是部分比较,就用in来判断,否则用Object.prototype.hasOwnProperty
      //判断object的key是否other里也有
      //如果没有,返回false
      return false;
    }
  }
  // Assume cyclic values are equal.
  var stacked = stack.get(object);
  if (stacked && stack.get(other)) {
    return stacked == other;
  }
  var result = true;//开始循环之前假设循环的值都是相等的
  stack.set(object, other);
  stack.set(other, object);

  var skipCtor = isPartial;//部分比较,跳过constructor
  while (++index < objLength) {//循环
    key = objProps[index];//object的key
    var objValue = object[key],//object key对应的value
        othValue = other[key];//other key对应的value

    if (customizer) {//如果有自定义比较方法,就用自定义的比较
      var compared = isPartial
        ? customizer(othValue, objValue, key, other, object, stack)
        : customizer(objValue, othValue, key, object, other, stack);
    }
    // Recursively compare objects (susceptible to call stack limits).
    if (!(compared === undefined
          ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack))
          : compared
        )) {
          //如果自定义比较失败就跳出循环,result=false
          //如果不是自定义比较,就用===比较,或者继续递归调用baseIsEqual来深度比较
      result = false;
      break;
    }
    skipCtor || (skipCtor = key == 'constructor');
  }
  if (result && !skipCtor) {//不是部分比较且object中有constructor属性,不跳过constructor属性,判断constructor是否一样
    var objCtor = object.constructor,
        othCtor = other.constructor;

    // Non `Object` object instances with different constructors are not equal.
    if (objCtor != othCtor &&
        ('constructor' in object && 'constructor' in other) &&
        !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
          typeof othCtor == 'function' && othCtor instanceof othCtor)) {
      result = false;
    }
  }
  stack['delete'](object);
  stack['delete'](other);
  return result;
}

/**
 * Gets the data for `map`.
 *
 * @private
 * @param {Object} map The map to query.
 * @param {string} key The reference key.
 * @returns {*} Returns the map data.
 */
//从map实例里找到key对应的值
function getMapData(map, key) {
  var data = map.__data__;
  return isKeyable(key)
    ? data[typeof key == 'string' ? 'string' : 'hash']
    : data.map;
     //根据key的类型返回对应的数据,要么是Map对象,要么是ListCache对象,要么是Hash对象
}

/**
 * Gets the property names, values, and compare flags of `object`.
 *
 * @private
 * @param {Object} object The object to query.
 * @returns {Array} Returns the match data of `object`.
 */
//获取对象中的属性名,属性值,和用来匹配的标记
function getMatchData(object) {
  var result = keys(object),//object的key组成的数组
      length = result.length;//key数组的长度

  while (length--) {//循环
    var key = result[length],//当前key
        value = object[key];//当前值

    result[length] = [key, value, isStrictComparable(value)];
    //重写result的当前值,变成[key, value, boolean]的形式,第三个布尔值表明当前值是否适合用===比较
  }
  return result;
}

/**
 * Gets the native function at `key` of `object`.
 *
 * @private
 * @param {Object} object The object to query.
 * @param {string} key The key of the method to get.
 * @returns {*} Returns the function if it's native, else `undefined`.
 */
function getNative(object, key) {
  var value = getValue(object, key);
  return baseIsNative(value) ? value : undefined;
}

/**
 * Gets the `toStringTag` of `value`.
 *
 * @private
 * @param {*} value The value to query.
 * @returns {string} Returns the `toStringTag`.
 */
var getTag = baseGetTag;
//处理toStringTag的兼容问题
// Fallback for data views, maps, sets, and weak maps in IE 11,
// for data views in Edge < 14, and promises in Node.js.
if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
    (Map && getTag(new Map) != mapTag) ||
    (Promise && getTag(Promise.resolve()) != promiseTag) ||
    (Set && getTag(new Set) != setTag) ||
    (WeakMap && getTag(new WeakMap) != weakMapTag)) {//如果在某些环境下无法判断这5种类型,就做一下兼容处理
  getTag = function(value) {
    var result = objectToString.call(value),
        Ctor = result == objectTag ? value.constructor : undefined,
        ctorString = Ctor ? toSource(Ctor) : undefined;
//获取值的constructor构造函数属性,如果获取到了就返回对应的tag,如果获取不到就返回原始toString值
    if (ctorString) {
      switch (ctorString) {
        case dataViewCtorString: return dataViewTag;
        case mapCtorString: return mapTag;
        case promiseCtorString: return promiseTag;
        case setCtorString: return setTag;
        case weakMapCtorString: return weakMapTag;
      }
    }
    return result;
  };
}

/**
 * Checks if `path` exists on `object`.
 *
 * @private
 * @param {Object} object The object to query.
 * @param {Array|string} path The path to check.
 * @param {Function} hasFunc The function to check properties.
 * @returns {boolean} Returns `true` if `path` exists, else `false`.
 */
//判断object中是否存在path路径,考虑深层path路径
//object给定对象,path给定路径,hasFunc只判断一层路径的基础方法
function hasPath(object, path, hasFunc) {
  path = isKey(path, object) ? [path] : castPath(path);//将路径转换成路径数组

  var result,//结果布尔值
      index = -1,//循环索引
      length = path.length;//路径数组的长度

  while (++index < length) {//循环路径数组
    var key = toKey(path[index]);//转换key值为合法key值
    if (!(result = object != null && hasFunc(object, key))) {
      //如果有哪一层不存在,就跳出循环且result赋值false
      break;
    }
    //否则继续获取下一层
    object = object[key];
  }
  if (result) {//如果循环结束后result为true,说明存在此路径,返回true
    return result;
  }
  var length = object ? object.length : 0;//object的length
  return !!length && isLength(length) && isIndex(key, length) &&
    (isArray(object) || isArguments(object));
  //判断object是数组的情况
  //有length属性,length属性是有效数字,判断最后一个key值是否是一个数组有效索引,object是数组或者是一个arguments对象
}

/**
 * Checks if `value` is a flattenable `arguments` object or array.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
 */
//检查一个变量是否是一个可展开的对象或者数组
function isFlattenable(value) {
  return isArray(value) || isArguments(value) ||
    !!(spreadableSymbol && value && value[spreadableSymbol]);
    //如果是数组,则可展开
    //如果是arguments对象,则可展开
    //如果当前环境含有Symbol对象,且此变量含有Symbol.isConcatSpreadable属性,Symbol.isConcatSpreadable用于改变array或者array-like对象使用concat时的默认行为
}

/**
 * Checks if `value` is a valid array-like index.
 *
 * @private
 * @param {*} value The value to check.
 * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
 * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
 */
//检查一个值是否是一个有效的array-like的索引
function isIndex(value, length) {
  length = length == null ? MAX_SAFE_INTEGER : length;//如果length是空就换成最大安全数字
  return !!length &&
    (typeof value == 'number' || reIsUint.test(value)) &&
    (value > -1 && value % 1 == 0 && value < length);
    //length存在
    //value是number值或者value符合reIsUint = /^(?:0|[1-9]\d*)$/正则
    //value是正数,value是整数,value小于length
}

/**
 * Checks if `value` is a property name and not a property path.
 *
 * @private
 * @param {*} value The value to check.
 * @param {Object} [object] The object to query keys on.
 * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
 */
//用来判断一个值是一个属性名,而不是一个属性的路径,这个主要和_.get等方法匹配,看懂了get方法就明白这里的路径是什么意思
/* 
  var object = { 'a': [{ 'b': { 'c': 3 } }] };

 _.get(object, 'a[0].b.c');
  // => 3

  _.get(object, ['a', '0', 'b', 'c']);
  // => 3

  _.get(object, 'a.b.c', 'default');
  // => 'default'
*/
function isKey(value, object) {//value用于检查的值,object用于在其中查询是否存在value属性的对象
  if (isArray(value)) {//如果value是数组,返回false,说明value是一个属性路径
    return false;
  }
  var type = typeof value;
  if (type == 'number' || type == 'symbol' || type == 'boolean' ||
      value == null || isSymbol(value)) {//如果value是以上类型,则是属性名,返回true
    return true;
  }
  return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
    (object != null && value in Object(object));
    //判断字符串的情况,第一个正则reIsPlainProp判断如果都是单词说明是属性名
    //第二个正则reIsDeepProp判断如果含有点.或者含有数组说明不是属性名
    //第三种情况如果value在object里能找到说明是属性名
}

/**
 * Checks if `value` is suitable for use as unique object key.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
 */
//判断一个值是否合适作为一个唯一的对象key
//如果这个值是String或者Number或者Symbol或者Boolean值,且不是__proto__,就可以作为一个唯一的键
//如果这个值不是以上四种类型,但是它等于null,那也可以作为一个唯一的键
function isKeyable(value) {
  var type = typeof value;
  return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
    ? (value !== '__proto__')
    : (value === null);
}

/**
 * Checks if `func` has its source masked.
 *
 * @private
 * @param {Function} func The function to check.
 * @returns {boolean} Returns `true` if `func` is masked, else `false`.
 */
function isMasked(func) {
  return !!maskSrcKey && (maskSrcKey in func);
}

/**
 * Checks if `value` is likely a prototype object.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
 */
//检查一个值是否类似一个prototype对象
function isPrototype(value) {
  var Ctor = value && value.constructor,//含有constructor属性
      proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;

  return value === proto;//如果构造函数的prototype和value相等,说明value对象是一个prototype对象
}

/**
 * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` if suitable for strict
 *  equality comparisons, else `false`.
 */
//检查一个值用严格等于===来比较是否合适
//这个值不是NaN也不是对象就符合要求
function isStrictComparable(value) {
  return value === value && !isObject(value);
}

/**
 * A specialized version of `matchesProperty` for source values suitable
 * for strict equality comparisons, i.e. `===`.
 *
 * @private
 * @param {string} key The key of the property to get.
 * @param {*} srcValue The value to match.
 * @returns {Function} Returns the new spec function.
 */
//返回一个方法,这个方法用于比较传入对象object key值对应的值是否和给定值srcValue相等
function matchesStrictComparable(key, srcValue) {
  return function(object) {
    if (object == null) {//如果传入对象为空,返回false
      return false;
    }
    return object[key] === srcValue &&
      (srcValue !== undefined || (key in Object(object)));
      //给定值不为undefined且object中有此key值,且key对应值和给定值严格严格相等,就返回true
  };
}

/**
 * Converts `string` to a property path array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the property path array.
 */
//转换字符串值变为属性path数组
var stringToPath = memoize(function(string) {
  string = toString(string);//将值转换成字符串

  var result = [];//结果数组
  if (reLeadingDot.test(string)) {
    //reLeadingDot = /^\./,判断字符串的开头如果是一个点,结果数组第一个就push一个空字符串
    result.push('');
  }
  string.replace(rePropName, function(match, number, quote, string) {
    //rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
    //rePropName这个正则匹配三种形式
    //?:是非捕获分组
    //第一种形式:不是点.也不是[或者]的任意字符组成的字符串,例如:'aaassssdfdffdf'
    //第二种形式:判断取数组下标的情况,例如:a[0].b.c.a[0][1]
    //第三种形式:???
    result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
    //reEscapeChar = /\\(\\)?/g
    //match是匹配到的内容,number是第二种形式的第一个捕获分组,quote是第二种形式的第二个捕获分组
    //有quote说明是带引号的内容,再执行一次replace操作,???
  });
  return result;
});

/**
 * Converts `value` to a string key if it's not a string or symbol.
 *
 * @private
 * @param {*} value The value to inspect.
 * @returns {string|symbol} Returns the key.
 */
//如果一个值不是string或者symbol,就把它转换成string
function toKey(value) {
  if (typeof value == 'string' || isSymbol(value)) {//如果这个值是字符串或者symbol,直接返回
    return value;
  }
  var result = (value + '');//转换成字符串
  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;//判断value是负0的情况
}

/**
 * Converts `func` to its source code.
 *
 * @private
 * @param {Function} func The function to process.
 * @returns {string} Returns the source code.
 */
function toSource(func) {
  if (func != null) {
    try {
      return funcToString.call(func);
    } catch (e) {}
    try {
      return (func + '');
    } catch (e) {}
  }
  return '';
}

/**
 * This method is like `_.difference` except that it accepts `iteratee` which
 * is invoked for each element of `array` and `values` to generate the criterion
 * by which they're compared. Result values are chosen from the first array.
 * The iteratee is invoked with one argument: (value).
 *
 * **Note:** Unlike `_.pullAllBy`, this method returns a new array.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Array
 * @param {Array} array The array to inspect.
 * @param {...Array} [values] The values to exclude.
 * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
 * @returns {Array} Returns the new array of filtered values.
 * @example
 *
 * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
 * // => [1.2]
 *
 * // The `_.property` iteratee shorthand.
 * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
 * // => [{ 'x': 2 }]
 */
//和_.difference方法类似,区别是传入一个迭代器在比较的时候给values数组的每一个元素都执行迭代器,用处理后的值来比较
var differenceBy = baseRest(function(array, values) {
  var iteratee = last(values);//最后一个参数是迭代器
  if (isArrayLikeObject(iteratee)) {//如果迭代器是一个array-like对象,就赋值为undefined
    iteratee = undefined;
  }
  return isArrayLikeObject(array)
    ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), baseIteratee(iteratee, 2))
    : [];
    //如果起始数组array不是array-like对象,就返回空数组
    //如果是array-like对象,就调用baseDifference方法
    //第一个参数是array起始数组,第二个参数是用来排除的数组values展开一层后的数组,第三个参数是迭代器
    //baseIteratee会处理iteratee参数,有下面几种情况:
    //如果iteratee是function,直接返回
    //如果iteratee是数组,那么数组第一个值是一个path属性路径,第二个值是srcValue,最后返回的方法会判断value[1]是否和传入的对象根据value[0]路径获取到的属性 是相等的
    //如果iteratee是一个对象,则iteratee就是source参数,这个方法会使用部分的(partial)深度比较来比较给定的对象和source对象,如果给定对象拥有相等的属性值,就返回true,否则false
    //如果iteratee是其他值,就返回一个方法,这个方法可以根据给定path属性路径返回给定对象的属性值
});

/**
 * Gets the last element of `array`.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Array
 * @param {Array} array The array to query.
 * @returns {*} Returns the last element of `array`.
 * @example
 *
 * _.last([1, 2, 3]);
 * // => 3
 */
//获取一个数组的结尾元素
function last(array) {
  var length = array ? array.length : 0;
  return length ? array[length - 1] : undefined;
}

/**
 * Creates a function that memoizes the result of `func`. If `resolver` is
 * provided, it determines the cache key for storing the result based on the
 * arguments provided to the memoized function. By default, the first argument
 * provided to the memoized function is used as the map cache key. The `func`
 * is invoked with the `this` binding of the memoized function.
 *
 * **Note:** The cache is exposed as the `cache` property on the memoized
 * function. Its creation may be customized by replacing the `_.memoize.Cache`
 * constructor with one whose instances implement the
 * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
 * method interface of `delete`, `get`, `has`, and `set`.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Function
 * @param {Function} func The function to have its output memoized.
 * @param {Function} [resolver] The function to resolve the cache key.
 * @returns {Function} Returns the new memoized function.
 * @example
 *
 * var object = { 'a': 1, 'b': 2 };
 * var other = { 'c': 3, 'd': 4 };
 *
 * var values = _.memoize(_.values);
 * values(object);
 * // => [1, 2]
 *
 * values(other);
 * // => [3, 4]
 *
 * object.a = 2;
 * values(object);
 * // => [1, 2]
 *
 * // Modify the result cache.
 * values.cache.set(object, ['a', 'b']);
 * values(object);
 * // => ['a', 'b']
 *
 * // Replace `_.memoize.Cache`.
 * _.memoize.Cache = WeakMap;
 */
//创建一个可以缓存func的结果的函数,resolver用来解析缓存result的键
function memoize(func, resolver) {
  if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
    //func和resolver(如果提供的话)如果不是函数,抛错误
    throw new TypeError(FUNC_ERROR_TEXT);
  }
  var memoized = function() {
    var args = arguments,
        key = resolver ? resolver.apply(this, args) : args[0],
        //如果提供了resolver,就用resolver来确定key,否则key就是memoized的第一个参数
        cache = memoized.cache;//cashe对象

    if (cache.has(key)) {//如果cache中已经有此key,就直接获取后返回
      return cache.get(key);
    }
    var result = func.apply(this, args);//否则调用func计算出key对应的value
    memoized.cache = cache.set(key, result);//将新value存入cache后返回
    return result;
  };
  memoized.cache = new (memoize.Cache || MapCache);//cache使用自定义的MapCache类型
  return memoized;
}

// Assign cache to `_.memoize`.
memoize.Cache = MapCache;

/**
 * Performs a
 * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
 * comparison between two values to determine if they are equivalent.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to compare.
 * @param {*} other The other value to compare.
 * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
 * @example
 *
 * var object = { 'a': 1 };
 * var other = { 'a': 1 };
 *
 * _.eq(object, object);
 * // => true
 *
 * _.eq(object, other);
 * // => false
 *
 * _.eq('a', 'a');
 * // => true
 *
 * _.eq('a', Object('a'));
 * // => false
 *
 * _.eq(NaN, NaN);
 * // => true
 */
//判断两个值是否相等
function eq(value, other) {
  return value === other || (value !== value && other !== other);
}

/**
 * Checks if `value` is likely an `arguments` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an `arguments` object,
 *  else `false`.
 * @example
 *
 * _.isArguments(function() { return arguments; }());
 * // => true
 *
 * _.isArguments([1, 2, 3]);
 * // => false
 */
//判断一个值是一个类似arguments对象 
function isArguments(value) {
  // Safari 8.1 makes `arguments.callee` enumerable in strict mode.
  return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
    (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
    //value是一个array-like对象,value有名为callee的自身属性,callee属性不可枚举,对象的toString标签是'[object Arguments]'
}

/**
 * Checks if `value` is classified as an `Array` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an array, else `false`.
 * @example
 *
 * _.isArray([1, 2, 3]);
 * // => true
 *
 * _.isArray(document.body.children);
 * // => false
 *
 * _.isArray('abc');
 * // => false
 *
 * _.isArray(_.noop);
 * // => false
 */
//判断一个值是不是数组对象
var isArray = Array.isArray;

/**
 * Checks if `value` is array-like. A value is considered array-like if it's
 * not a function and has a `value.length` that's an integer greater than or
 * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
 * @example
 *
 * _.isArrayLike([1, 2, 3]);
 * // => true
 *
 * _.isArrayLike(document.body.children);
 * // => true
 *
 * _.isArrayLike('abc');
 * // => true
 *
 * _.isArrayLike(_.noop);
 * // => false
 */
//判断一个值是否是一个array-like
//规则:不等于null,不是function类型,并且有length属性,length是大于0小于Number.MAX_SAFE_INTEGER的整数
function isArrayLike(value) {
  return value != null && isLength(value.length) && !isFunction(value);
}

/**
 * This method is like `_.isArrayLike` except that it also checks if `value`
 * is an object.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an array-like object,
 *  else `false`.
 * @example
 *
 * _.isArrayLikeObject([1, 2, 3]);
 * // => true
 *
 * _.isArrayLikeObject(document.body.children);
 * // => true
 *
 * _.isArrayLikeObject('abc');
 * // => false
 *
 * _.isArrayLikeObject(_.noop);
 * // => false
 */
//判断一个值是不是一个array-like对象
//isObjectLike判断一个值是否是一个object-like,规则是:typeof返回object,并且不是null
//isArrayLike判断一个值是否是一个array-like,规则:不等于null,不是function类型,并且有length属性,length是大于0小于Number.MAX_SAFE_INTEGER的整数
function isArrayLikeObject(value) {
  return isObjectLike(value) && isArrayLike(value);
}

/**
 * Checks if `value` is classified as a `Function` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a function, else `false`.
 * @example
 *
 * _.isFunction(_);
 * // => true
 *
 * _.isFunction(/abc/);
 * // => false
 */
function isFunction(value) {
  // The use of `Object#toString` avoids issues with the `typeof` operator
  // in Safari 8-9 which returns 'object' for typed array and other constructors.
  var tag = isObject(value) ? objectToString.call(value) : '';
  return tag == funcTag || tag == genTag;
}

/**
 * Checks if `value` is a valid array-like length.
 *
 * **Note:** This method is loosely based on
 * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
 * @example
 *
 * _.isLength(3);
 * // => true
 *
 * _.isLength(Number.MIN_VALUE);
 * // => false
 *
 * _.isLength(Infinity);
 * // => false
 *
 * _.isLength('3');
 * // => false
 */
//判断一个值是否是一个有效的array-like对象的length属性
function isLength(value) {
  return typeof value == 'number' &&
    value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
    //是number类型,是正数,是整数不是小数,在最大数字范围内
}

/**
 * Checks if `value` is the
 * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
 * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an object, else `false`.
 * @example
 *
 * _.isObject({});
 * // => true
 *
 * _.isObject([1, 2, 3]);
 * // => true
 *
 * _.isObject(_.noop);
 * // => true
 *
 * _.isObject(null);
 * // => false
 */
//判断一个值是否是一个对象
function isObject(value) {
  var type = typeof value;
  return !!value && (type == 'object' || type == 'function');
}

/**
 * Checks if `value` is object-like. A value is object-like if it's not `null`
 * and has a `typeof` result of "object".
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
 * @example
 *
 * _.isObjectLike({});
 * // => true
 *
 * _.isObjectLike([1, 2, 3]);
 * // => true
 *
 * _.isObjectLike(_.noop);
 * // => false
 *
 * _.isObjectLike(null);
 * // => false
 */
//判断一个值是否是一个object-like,规则是:typeof返回object,并且不是null
function isObjectLike(value) {
  return !!value && typeof value == 'object';
}

/**
 * Checks if `value` is classified as a `Symbol` primitive or object.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
 * @example
 *
 * _.isSymbol(Symbol.iterator);
 * // => true
 *
 * _.isSymbol('abc');
 * // => false
 */
//判断一个值是否是原生的Symbol对象
function isSymbol(value) {
  return typeof value == 'symbol' ||
    (isObjectLike(value) && objectToString.call(value) == symbolTag);
}

/**
 * Checks if `value` is classified as a typed array.
 *
 * @static
 * @memberOf _
 * @since 3.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
 * @example
 *
 * _.isTypedArray(new Uint8Array);
 * // => true
 *
 * _.isTypedArray([]);
 * // => false
 */
//判断一个值是typedArray类型,如果是nodejs环境就使用nodejs自带的优化方法
var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;

/**
 * Converts `value` to a string. An empty string is returned for `null`
 * and `undefined` values. The sign of `-0` is preserved.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to process.
 * @returns {string} Returns the string.
 * @example
 *
 * _.toString(null);
 * // => ''
 *
 * _.toString(-0);
 * // => '-0'
 *
 * _.toString([1, 2, 3]);
 * // => '1,2,3'
 */
//将一个值转换成字符串值,空值会返回空字符串
function toString(value) {
  return value == null ? '' : baseToString(value);
}

/**
 * Gets the value at `path` of `object`. If the resolved value is
 * `undefined`, the `defaultValue` is returned in its place.
 *
 * @static
 * @memberOf _
 * @since 3.7.0
 * @category Object
 * @param {Object} object The object to query.
 * @param {Array|string} path The path of the property to get.
 * @param {*} [defaultValue] The value returned for `undefined` resolved values.
 * @returns {*} Returns the resolved value.
 * @example
 *
 * var object = { 'a': [{ 'b': { 'c': 3 } }] };
 *
 * _.get(object, 'a[0].b.c');
 * // => 3
 *
 * _.get(object, ['a', '0', 'b', 'c']);
 * // => 3
 *
 * _.get(object, 'a.b.c', 'default');
 * // => 'default'
 */
//根据path来获取object上对应健的值
function get(object, path, defaultValue) {
  var result = object == null ? undefined : baseGet(object, path);//根据路径获取值
  return result === undefined ? defaultValue : result;//如果结果是undefined,返回默认值
}

/**
 * Checks if `path` is a direct or inherited property of `object`.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Object
 * @param {Object} object The object to query.
 * @param {Array|string} path The path to check.
 * @returns {boolean} Returns `true` if `path` exists, else `false`.
 * @example
 *
 * var object = _.create({ 'a': _.create({ 'b': 2 }) });
 *
 * _.hasIn(object, 'a');
 * // => true
 *
 * _.hasIn(object, 'a.b');
 * // => true
 *
 * _.hasIn(object, ['a', 'b']);
 * // => true
 *
 * _.hasIn(object, 'b');
 * // => false
 */
//判断path路径在给定object中是否存在
function hasIn(object, path) {
  return object != null && hasPath(object, path, baseHasIn);
}

/**
 * Creates an array of the own enumerable property names of `object`.
 *
 * **Note:** Non-object values are coerced to objects. See the
 * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
 * for more details.
 *
 * @static
 * @since 0.1.0
 * @memberOf _
 * @category Object
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 * @example
 *
 * function Foo() {
 *   this.a = 1;
 *   this.b = 2;
 * }
 *
 * Foo.prototype.c = 3;
 *
 * _.keys(new Foo);
 * // => ['a', 'b'] (iteration order is not guaranteed)
 *
 * _.keys('hi');
 * // => ['0', '1']
 */
//将对象可枚举的属性的key提取出来创建一个新数组,与Object.keys一样的效果
function keys(object) {
  return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
  //先判断object是否是array-like对象,如果是调用arrayLikeKeys,如果不是调用baseKeys
}

/**
 * This method returns the first argument it receives.
 *
 * @static
 * @since 0.1.0
 * @memberOf _
 * @category Util
 * @param {*} value Any value.
 * @returns {*} Returns `value`.
 * @example
 *
 * var object = { 'a': 1 };
 *
 * console.log(_.identity(object) === object);
 * // => true
 */
//用作默认迭代器,直接返回接收到的第一个参数
function identity(value) {
  return value;
}

/**
 * Creates a function that returns the value at `path` of a given object.
 *
 * @static
 * @memberOf _
 * @since 2.4.0
 * @category Util
 * @param {Array|string} path The path of the property to get.
 * @returns {Function} Returns the new accessor function.
 * @example
 *
 * var objects = [
 *   { 'a': { 'b': 2 } },
 *   { 'a': { 'b': 1 } }
 * ];
 *
 * _.map(objects, _.property('a.b'));
 * // => [2, 1]
 *
 * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
 * // => [1, 2]
 */
//创建一个方法,这个方法可以根据给定path属性路径返回给定对象的属性值
function property(path) {
  return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
  //先判断path是key还是path
  //如果是key,调用baseProperty,返回一个方法,这个方法返回object的对应key的对应value
  //如果是路径,调用basePropertyDeep,返回一个方法,这个方法根据path返回object的对应属性值
}

module.exports = differenceBy;