preface

Lodash source code

.internal

arrayIncludesWith

/** * similar to arrayIncludes * except that you can pass in a comparison function **@private
 * @param {Array} [array] The array to search for *@param {*} Target Indicates the value to be searched *@param {Function} The comparison function * called by the comparator for each element@returns {boolean} Returns true if target was found otherwise false */
function arrayIncludesWith(array, target, comparator) {
  // Takes advantage of JavaScript's automatic type conversion when ==
  // Filter undefined and null
  if (array == null) {
    return false;
  }

  for (const value of array) {
    if (comparator(target, value)) {
      return true; }}return false;
}

export default arrayIncludesWith;
Copy the code

arrayLikeKeys

import isArguments from '.. /isArguments.js';
import isBuffer from '.. /isBuffer.js';
import isIndex from './isIndex.js';
import isTypedArray from '.. /isTypedArray.js';

/** Get hasOwnProperty from Object's prototype */
const hasOwnProperty = Object.prototype.hasOwnProperty;

/** * Creates an array of enumerable property names ** for array-like values@private
 * @param {*} Value Indicates the value to be queried *@param {boolean} Inherited specifies that inherited property names * are returned@returns {Array} Returns an array of enumerable property names */
function arrayLikeKeys(value, inherited) {
  // Check whether the value is array or array-like
  const isArr = Array.isArray(value);
  constisArg = ! isArr && isArguments(value);constisBuff = ! isArr && ! isArg && isBuffer(value);constisType = ! isArr && ! isArg && ! isBuff && isTypedArray(value);// If value is array or array-like, index needs to be collected
  const skipIndexes = isArr || isArg || isBuff || isType;
  const length = value.length;
  // Instantiate a new array of the same length as value.length or 0
  // The length of the new array depends on whether value is array or array-like
  const result = new Array(skipIndexes ? length : 0);
  let index = skipIndexes ? -1 : length;
  / / collect index
  while (++index < length) {
    result[index] = `${index}`;
  }
  for (const key in value) {
    if (
      // When Inherited is true, inherited property names are allowed to be iterated
      Otherwise, only its own attribute name is allowed to be traversed
      (inherited || hasOwnProperty.call(value, key)) &&
      // If value is array or array-like
      // And the current attribute is length or index
      // Skip the current attribute name! ( skipIndexes &&// Safari 9 is enumerable in strict mode. Arguments.length
        (key === 'length' ||
          / / skip indexisIndex(key, length)) ) ) { result.push(key); }}return result;
}

export default arrayLikeKeys;
Copy the code

The arrayLikeKeys function is hard to understand by reading the source code, so you’ll need to parse it line by line.

const isArr = Array.isArray(value);
constisArg = ! isArr && isArguments(value);constisBuff = ! isArr && ! isArg && isBuffer(value);constisType = ! isArr && ! isArg && ! isBuff && isTypedArray(value);const skipIndexes = isArr || isArg || isBuff || isType;
Copy the code

To check whether value is array/array-like, collect index to result. If value is array/array-like, collect index to result.

const length = value.length;
const result = new Array(skipIndexes ? length : 0);
Copy the code

Instantiate an array as long as value if you want to collect index, or 0 if you don’t.

let index = skipIndexes ? -1 : length;
while (++index < length) {
  result[index] = `${index}`;
}
Copy the code

Similarly, if the index needs to be collected, the index needs to be initialized to -1 and traversed from left to right. Otherwise, the index collection process (traversal) is skipped.

But you can see the use of for… In iterates over the key of the value. Index should also be iterated over. Why use a while loop to iterate over the index?

Here is a more in-depth knowledge point:

const arr = new Array(100);
arr[0] = 0;
arr[50] = 50;
arr[99] = 99;
for (const index in arr) {
  console.log(index);
}
/ / = > 0
/ / = > 50
/ / = > 99
Copy the code

When the array is sparse, for… In does not iterate over all indexes.

for (const key in value) {
  if( (inherited || hasOwnProperty.call(value, key)) && ! (skipIndexes && (key ==='length'|| isIndex(key, length))) ) { result.push(key); }}Copy the code

Other attributes on value require for… In the case of… In also iterates over the properties on the value prototype, so you need to make another judgment on the properties.

inherited || hasOwnProperty.call(value, key);
Copy the code

When Inherited is True, the inherited properties are allowed to be iterated; otherwise, the inherited properties are allowed to be iterated.

! (skipIndexes && (key ==='length' || isIndex(key, length)));
Copy the code

When value is array/array-like, index is already traversed, so skip this step.

But Safari 9 is enumerable in strict mode, so arguments.length gets iterated and needs to be culled.

Eventually all qualified attribute names are pushed into result and returned.

The arrayLikeKeys function introduces isArguments, isBuffer, isIndex, and isTypedArray functions, so the implementation of these functions also needs to be known.

import getTag from './.internal/getTag.js';
import isObjectLike from './isObjectLike.js';

/** * Check whether value is the arguments object **@since 0.1.0 from *@category Lang
 * @param {*} Value Indicates the value to be checked *@returns {boolean} Returns true if value is arguments object otherwise returns false *@example
 *
 * isArguments(function() { return arguments }())
 * // => true
 *
 * isArguments([1, 2, 3])
 * // => false
 */
function isArguments(value) {
  return isObjectLike(value) && getTag(value) == '[object Arguments]';
}

export default isArguments;
Copy the code

The isArguments function also introduces isObjectLike and getTag functions, so the implementation of these functions also needs to be understood.

/** * Check if value is object-like. * Object-like value is not null and typeof results are "object" **@since 4.0.0
 * @category Lang
 * @param {*} Value Indicates the value to be checked *@returns {boolean} Return true if value is object-like and false * otherwise@example
 *
 * isObjectLike({})
 * // => true
 *
 * isObjectLike([1, 2, 3])
 * // => true
 *
 * isObjectLike(Function)
 * // => false
 *
 * isObjectLike(null)
 * // => false
 */
function isObjectLike(value) {
  // JavaScript legacy issues
  // typeof null === "object"
  // In the original JavaScript, values were stored as a 32-bit memory unit
  // Each memory cell contains 1 to 3 bits of actual data content for type markers and values
  // The value marked with type 000 is object and the actual data content is a reference to the object
  // null indicates that all 32 bits are 0
  return typeof value === 'object'&& value ! = =null;
}

export default isObjectLike;
Copy the code
/* Get toString from Object's prototype to get the type token */
const toString = Object.prototype.toString;

/** * get toStringTag ** of value@private
 * @param {*} Value Indicates the value to be checked *@returns {string} Returns the toStringTag * /
function getTag(value) {
  // Takes advantage of JavaScript's automatic type conversion when ==
  // Filter undefined and null
  if (value == null) {
    return value === undefined ? '[object Undefined]' : '[object Null]';
  }
  return toString.call(value);
}

export default getTag;
Copy the code

In view of the complexity of isBuffer, isIndex and isTypedArray functions, see the next decomposition.