The resources

Github repository address

Source copy repository address (read source version)

Chinese document

English document

difference(array, [values1, values2…] )

Difference creates a new array with each element in the array that exists only in array and not in other arrays.

There are many helper methods and data structures in the difference method.

The data structure

Hash

The Hash object is instantiated with an array of key-value pairs of elements. The first element of the key-value pair array is the key name and the second element is the key value

The instance method of the Hash object has set Get has delete clear

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

/ / the Hash object
// Instantiating the Hash object is passing in an array whose elements are key-value pairs.
// The first element of the array is the key name and the second element is the key value
Create instance attributes when instantiating the Hash object: __data__ and size attributes
// Add the key-value pairs from the incoming array to the __data__ object and calculate the total number of key-value pairs assigned to the size property
// Instance methods of the Hash object have set get has delete clear
class Hash {

  /**
   * Creates a hash object.
   *
   * @private
   * @constructor
   * @param {Array} [entries] The key-value pairs to cache.
   */
  constructor(entries) {
    let index = -1
    const length = entries == null ? 0 : entries.length

    this.clear() // Initialize the __data__ and size attributes
    while (++index < length) {
      // Loop over to retrieve the key-value pair and set it to the __data__ object
      const entry = entries[index]
      this.set(entry[0], entry[1])}}/**
   * Removes all key-value entries from the hash.
   *
   * @memberOf Hash* /
  clear() {
    // Empty the __data__ object and return size to zero
    this.__data__ = Object.create(null)
    this.size = 0
  }

  /**
   * Removes `key` and its value from the hash.
   *
   * @memberOf Hash
   * @param {string} key The key of the value to remove.
   * @returns {boolean} Returns `true` if the entry was removed, else `false`.
   */
  delete(key) {
    // Delete key-value pairs
    const result = this.has(key) && delete this.__data__[key]
    this.size -= result ? 1 : 0
    return result
  }

  /**
   * Gets the hash value for `key`.
   *
   * @memberOf Hash
   * @param {string} key The key of the value to get.
   * @returns {*} Returns the entry value.
   */
  get(key) {
    // Get the key-value pairs
    const data = this.__data__
    const result = data[key]
    return result === HASH_UNDEFINED ? undefined : result
  }

  /**
   * Checks if a hash value for `key` exists.
   *
   * @memberOf Hash
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  has(key) {
    // Check whether there is a key value pair
    const data = this.__data__
    returndata[key] ! = =undefined
  }

  /**
   * Sets the hash `key` to `value`.
   *
   * @memberOf Hash
   * @param {string} key The key of the value to set.
   * @param {*} value The value to set.
   * @returns {Object} Returns the hash instance.
   */
  set(key, value) {
    // Set key-value pairs
    const data = this.__data__
    this.size += this.has(key) ? 0 : 1
    data[key] = value === undefined ? HASH_UNDEFINED : value
    return this}}export default Hash
Copy the code

MapCache

Like Hash, MapCache takes an array of key-value pairs as an instantiation object. But MapCache accepts key-value pairs. The key-name may be an object or array, or it may be a common data type (string, Boolean, number, symbol).

When the MapCache is instantiated, different types of key-value pairs are passed in. MapCache stores key-value pairs according to their key-name types

If the key name is an object type, the Map data type is used to access the key-value pairs

If the key name is not an object type, Hash data structures are used to access key-value pairs

import Hash from './Hash.js'

/**
 * Gets the data for `map`.
 *
 * @private
 * @param {Object} map The map to query.
 * @param {string} key The reference key.
 * @returns {*} Returns the map data.
 */
function getMapData({ __data__ }, key) {
  // Pass in two arguments, mapCache object and key
  // Obtain map data of the corresponding key type
  // If key is string, mapCache.__data__. String is returned
  // If the key is number, Boolean, or symbol, return mapCache.__data__. Hash
  // If the key is not of the above type, mapCache.__data__.map is returned
  const data = __data__
  return isKeyable(key)
    ? data[typeof key === 'string' ? 'string' : 'hash']
    : data.map
}

/**
 * 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`.
 */
function isKeyable(value) {
  // Determine whether the value passed in can be used as an attribute name for the object
  String number Boolean Symbol Type
  // And the value cannot be '__proto__'
  // Or the value is null
  const type = typeof value
  return (type === 'string' || type === 'number' || type === 'symbol' || type === 'boolean')? (value ! = ='__proto__')
    : (value === null)}// Save the key-value pairs passed in
// The key name may be an object or array, or a common data type (string, Boolean, number, symbol).
class MapCache {
  /**
   * Creates a map cache object to store key-value pairs.
   *
   * @private
   * @constructor
   * @param {Array} [entries] The key-value pairs to cache.
   */
  constructor(entries) {
    let index = -1
    const length = entries == null ? 0 : entries.length

    this.clear() // Initialize the __data__ and size attributes
    while (++index < length) {
      const entry = entries[index]
      this.set(entry[0], entry[1])}}/**
   * Removes all key-value entries from the map.
   *
   * @memberOf MapCache* /
  clear() {
    this.size = 0
    this.__data__ = {
      'hash': new Hash, // Store key-value pairs of type string
      'map': new Map.// The key name is Boolean, number, symbol
      'string': new Hash // Map instance keys may be object, array, or other data types}}/**
   * Removes `key` and its value from the map.
   *
   * @memberOf MapCache
   * @param {string} key The key of the value to remove.
   * @returns {boolean} Returns `true` if the entry was removed, else `false`.
   */
  delete(key) {
    // Delete the key-value pair named key
    const result = getMapData(this, key)['delete'](key)
    this.size -= result ? 1 : 0
    return result
  }

  /**
   * Gets the map value for `key`.
   *
   * @memberOf MapCache
   * @param {string} key The key of the value to get.
   * @returns {*} Returns the entry value.
   */
  get(key) {
    // Get the value of the key named key
    return getMapData(this, key).get(key)
  }

  /**
   * Checks if a map value for `key` exists.
   *
   * @memberOf MapCache
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  has(key) {
    // Check whether there are key-value pairs with the key name key
    return getMapData(this, key).has(key)
  }

  /**
   * Sets the map `key` to `value`.
   *
   * @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.
   */
  set(key, value) {
    // Get the corresponding data warehouse according to the type of the key name
    const data = getMapData(this, key)
    // Data size
    const size = data.size

    // Set key-value pairs
    data.set(key, value)
    // Update the size attribute to determine whether the data size has changed
    this.size += data.size == size ? 0 : 1
    // Return the instance
    return this}}export default MapCache
Copy the code

SetCache

When SetCache is instantiated, it receives an array and stores the elements of the array in a loop.

The SetCache object has only two instance methods: add and HAS

import MapCache from './MapCache.js'

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

class SetCache {

  /**
   * Creates an array cache object to store unique values.
   *
   * @private
   * @constructor
   * @param {Array} [values] The values to cache.
   */
  constructor(values) {
    let index = -1
    const length = values == null ? 0 : values.length

    this.__data__ = new MapCache // instantiate MapCache assigned to the __data__ attribute
    while (++index < length) {
      // Loop through the number group to add the value to the __data__ property
      this.add(values[index])
    }
  }

  /**
   * Adds `value` to the array cache.
   *
   * @memberOf SetCache
   * @alias push
   * @param {*} value The value to cache.
   * @returns {Object} Returns the cache instance.
   */
  add(value) {
    // Add value as the key and HASH_UNDEFINED as the key to the MapCache object
    this.__data__.set(value, HASH_UNDEFINED)
    Return the current instance
    return this
  }

  /**
   * Checks if `value` is in the array cache.
   *
   * @memberOf SetCache
   * @param {*} value The value to search for.
   * @returns {boolean} Returns `true` if `value` is found, else `false`.
   */
  has(value) {
    // Returns whether the current instance has value
    return this.__data__.has(value)
  }
}

// Add the instance method push and assign it add
SetCache.prototype.push = SetCache.prototype.add

export default SetCache
Copy the code

cacheHas(cache, key)

Check whether a key exists in the cache. Applicable to SetCache, MapCache and other data structures

/**
 * 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`.
 */
function cacheHas(cache, key) {
  return cache.has(key)
}

export default cacheHas
Copy the code

Helper method

baseFlatten & isArrayLikeObject

The baseFlatten and isArrayLikeObject methods are described in loDash

baseIsNaN(value)

BaseIsNaN returns whether value is a NaN

/**
 * 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`.
 */
function baseIsNaN(value) {
  returnvalue ! == value }export default baseIsNaN
Copy the code

value ! == value when is this true? In the ECMA262 specification, only NaN! == NaN.

ECMA262 specification

For a detailed explanation, see this article in Lin Jingyi’s Notepad

baseFindIndex(array, predicate, fromIndex, fromRight)

BaseFindIndex returns the index of the elements in the array that can be verified by the predicate function.

FromIndex is the index to start traversing the element, and fromRight indicates whether the search is to the left or right

/**
 * The base implementation of `findIndex` and `findLastIndex`.
 *
 * @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`.
 */
function baseFindIndex(array, predicate, fromIndex, fromRight) {
  // Get the length of the array, according to the direction of the search to determine the start of the search position index
  const { length } = array
  let index = fromIndex + (fromRight ? 1 : -1)

  // Start iterating based on the search direction, calling the predicate function, passing in the array elements, index values, and the array
  // If the predicate returns true, the loop ends and the current index is returned
  while ((fromRight ? index-- : ++index < length)) {
    if (predicate(array[index], index, array)) {
      return index
    }
  }
  // No predicate index is satisfied after traversal, returns -1
  return -1
}

export default baseFindIndex
Copy the code

strictIndexOf(array, value, fromIndex)

Starting from fromIndex, traverse the elements in the array from left to right, find the index of the element exactly equal to value (===) and return. If no such element exists, return -1

/**
 * A specialized version of `indexOf` which performs strict equality
 * comparisons of values, i.e. `===`.
 *
 * @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`.
 */
function strictIndexOf(array, value, fromIndex) {
  // Get the index value and array length to start traversal
  let index = fromIndex - 1
  const { length } = array

  // Walk through the array and compare the elements and values strictly equally. If strictly equal, return the index value
  while (++index < length) {
    if (array[index] === value) {
      return index
    }
  }
  // When traversal is complete, return -1
  return -1
}

export default strictIndexOf
Copy the code

baseIndexOf(array, value, fromIndex)

Search for the index of value in the array starting from fromIndex. If there is no index, -1 is returned

import baseFindIndex from './baseFindIndex.js'
import baseIsNaN from './baseIsNaN.js'
import strictIndexOf from './strictIndexOf.js'

/**
 * 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`.
 */
function baseIndexOf(array, value, fromIndex) {
  If value is NaN, use the baseFindIndex method to find the index; if not, use strictIndexOf method to find the index
  return value === value
    ? strictIndexOf(array, value, fromIndex)
    : baseFindIndex(array, baseIsNaN, fromIndex)
}

export default baseIndexOf
Copy the code

arrayIncludes(array, value)

Check whether the array contains a value element

import baseIndexOf from './baseIndexOf.js'

/**
 * 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) {
  const length = array == null ? 0 : array.length
  // Check whether the length of the array is greater than 0 and the index of value in the array is greater than -1
  return!!!!! length && baseIndexOf(array, value,0) > -1
}

export default arrayIncludes
Copy the code

arrayIncludesWith(array, target, comparator)

ArrayIncludesWith includes a comparator function that is passed in with target to get the comparison result when traversing an array

/**
 * 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`.
 */
function arrayIncludesWith(array, target, comparator) {
  if (array == null) {
    return false
  }

  for (const value of array) {
    // Walk through the array and target function to get the comparison result
    // If the result is true, return the result
    if (comparator(target, value)) {
      return true}}// There is no element that satisfies the comparison of target and comparator after traversal
  / / returns false
  return false
}

export default arrayIncludesWith
Copy the code

The realization of the difference of

difference(array, values)

Difference can take multiple arrays as arguments, and return a new array. The elements in the array are the elements in the first argument of the function, but the other parameters are not present

import baseDifference from './.internal/baseDifference.js'
import baseFlatten from './.internal/baseFlatten.js'
import isArrayLikeObject from './isArrayLikeObject.js'

/** * Creates an array of `array` values not included in the other given arrays * using [' SameValueZero '](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) * For Equality order and references of result values are * determined by the first array. * * **Note:** Unlike `pullAll`, this method returns a new array. * *@since 0.1.0 from *@category Array
 * @param {Array} array The array to inspect.
 * @param {... Array} [values] The values to exclude.
 * @returns {Array} Returns the new array of filtered values.
 * @see union, unionBy, unionWith, without, xor, xorBy, xorWith,
 * @example
 *
 * difference([2, 1], [2, 3])
 * // => [1]
 */
function difference(array, ... values) {
  // Return an array
  // If array is an array-like object, get the result by baseDifference and return it. Otherwise return an empty array
  return isArrayLikeObject(array)
    // Form a new array with the elements of all array-like objects in values through baseFlatten
    ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) : []}export default difference
Copy the code

baseDifference(array, values, iteratee, comparator)

BaseDifference returns a new array of elements that are present in array but not in VALUES.

The iteratee function is the function that deals with the array and values elements. The comparator is the function that compares the custom array and values elements

import SetCache from './SetCache.js'
import arrayIncludes from './arrayIncludes.js'
import arrayIncludesWith from './arrayIncludesWith.js'
import map from '.. /map.js'
import cacheHas from './cacheHas.js'

/** Used as the size to enable large array optimizations. */
// Limits the maximum length of the array
const LARGE_ARRAY_SIZE = 200

/**
 * The base implementation of methods like `difference` without support
 * for excluding multiple arrays.
 *
 * @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.
 */
function baseDifference(array, values, iteratee, comparator) {
  // Initialize the includes traversal function to arrayIncludes
  let includes = arrayIncludes
  // Initialize isCommon to true
  // If a comparator function is passed in or values is longer than the limit array length
  // isCommon is changed to false and includes is changed to the corresponding value
  let isCommon = true
  const result = []
  const valuesLength = values.length

  if(! array.length) {// Array length is 0, return an empty array
    return result
  }
  if (iteratee) {
    // If iteratee is passed, values will be processed to generate a new array
    values = map(values, (value) = > iteratee(value))
  }
  if (comparator) {
    // If the comparator is passed in, set includes to arrayIncludesWith
    includes = arrayIncludesWith
    isCommon = false
  }
  else if (values.length >= LARGE_ARRAY_SIZE) {
    // If the value length exceeds the limit size
    // Set includes to cacheHas
    // Values are converted to SetCache data
    includes = cacheHas
    isCommon = false
    values = new SetCache(values)
  }
  outer:
  for (let value of array) {
    / / iterate through the array
    // Process the value through iteratee
    const computed = iteratee == null? value : iteratee(value) value = (comparator || value ! = =0)? value :0
    if (isCommon && computed === computed) {
      // isCommon is true and computed is not NaN
      let valuesIndex = valuesLength
      while (valuesIndex--) {
        // Run values and computed for comparison
        if (values[valuesIndex] === computed) {
          // If computed exists in VALUES, continue array traversal
          continue outer
        }
      }
      // Add value to result if values does not have computed
      result.push(value)
    }
    else if(! includes(values, computed, comparator)) {If isCommon is false, call includes to check whether computed exists in values. If not, add value to result
      result.push(value)
    }
  }
  / / return the result
  return result
}

export default baseDifference
Copy the code