This article will continue to analyze the zip family of methods, the zip method is mainly to synthesize the target array groups into arrays or objects. Methods for composing arrays include zip, unzip, zipWith, and unzipWith. Methods for composing objects include zipObject, zipObjectDeep, and core methods baseZipObject and baseSet.

Corresponding source code analysis has been pushed to github repository: github.com/MageeLin/lo…

The dependency path diagram for the ZIP family method is shown below:

zip

The zip method is interesting in that it is the same operation as unzip while being the inverse. Inside the zip method is the called return Unzip (Arrays).

unzip

Unzip is the basis for zip, zipWith, and unzipWith implementations. But unzip is essentially a two-step process:

  1. findarraysIn the longestarrayThe length of the;
  2. fromarraysEach of thearrayTake out the corresponding position of the elements assembled together to form a newarrays;
import filter from './filter.js';
import map from './map.js';
import baseProperty from './.internal/baseProperty.js';
import isArrayLikeObject from './isArrayLikeObject.js';

/** * This method is similar to 'zip', but it takes' arrays' from 'array' and creates a 'arrays' from' array ', which is the grouping structure before zip **@since 1.2.0
 * @category Array
 * @param {Array} Array The array * to be executed@returns {Array} Returns the regrouped array *@see unzipWith, zip, zipObject, zipObjectDeep, zipWith
 * @example
 *
 * const zipped = zip(['a', 'b'], [1, 2], [true, false])
 * // => [['a', 1, true], ['b', 2, false]]
 *
 * unzip(zipped)
 * // => [['a', 'b'], [1, 2], [true, false]]
 */
function unzip(array) {
  Return [] when array is an empty array
  if(! (array ! =null && array.length)) {
    return [];
  }
  let length = 0;
  // The iteration performs two functions. The first is to filter out the array
  // The second is to set length to the length of the largest array
  array = filter(array, (group) = > {
    if (isArrayLikeObject(group)) {
      length = Math.max(group.length, length);
      return true; }});let index = -1;
  // Create an empty array based on the longest length
  const result = new Array(length);
  / / iteration
  while (++index < length) {
    // 把每一个array都取对应的位置处的值map成一个新array
    // Then assign result[index]
    result[index] = map(array, baseProperty(index));
  }
  return result;
}

export default unzip;
Copy the code

zip

The zip method and unzip are both inverse operations, but they are the same operation. The reason is that essentially all the arrays are called unzip, but when the same arrays are repeatedly called by Unzip, they actually repeatedly form two arrays.

import unzip from './unzip.js';

/** * Create a grouping of 'arrays' whose first array contains all the first elements of a given array, whose second array contains all the second elements of a given array, and so on. * *@since 0.1.0 from *@category Array
 * @param {... Array} [Arrays] The arrays * to deal with@returns {Array} Returns the arrays * of the grouped arrays@see unzip, unzipWith, zipObject, zipObjectDeep, zipWith
 * @example
 *
 * zip(['a', 'b'], [1, 2], [true, false])
 * // => [['a', 1, true], ['b', 2, false]]
 */
function zip(. arrays) {
  // Returns the result of unzip(Arrays), which is essentially the same as ZIP
  return unzip(arrays);
}

export default zip;
Copy the code

unZipWith

The same principle applies to unZipWith and zipWith. But they all rely on unZip, just calling Iteratee once each time through the loop.

import map from './map.js';
import unzip from './unzip.js';

/** * This method is similar to 'unzip', but it takes an 'iteratee' iterative method argument * to specify how the recombined values should be combined. * Iteratee calls pass in the value of each group: (... Group). * *@since 3.8.0
 * @category Array
 * @param {... Array} [Arrays] The arrays * to deal with@param {Function} The iteratee method iterates through each array, deciding how to combine the grouping values *@returns {Array} Returns the regrouped array *@example
 *
 * const zipped = zip([1, 2], [10, 20], [100, 200])
 * // => [[1, 10, 100], [2, 20, 200]]
 *
 * unzipWith(zipped, add)
 * // => [3, 30, 300]
 */
function unzipWith(array, iteratee) {
  Return [] when array is an empty array
  if(! (array ! =null && array.length)) {
    return [];
  }
  Call unzip directly to return the result
  const result = unzip(array);
  // Return iteratee.apply(undefined, group) to convert the results in the group
  return map(result, (group) = > iteratee.apply(undefined, group));
}

export default unzipWith;
Copy the code

zipWith

The thing to watch out for in zipWith is the iteratee argument, and if the last argument is a function, pop it out of the Arrays.

import unzipWith from './unzipWith.js';

/** * This method is similar to 'zip', but it takes an 'iteratee' (iterating method), * to specify how grouped values should be combined. * Iteratee calls the elements of each group: (... group). * *@since 3.8.0
 * @category Array
 * @param {... Array} [Arrays] The arrays * to deal with@param {Function} The iteratee method iterates through each array, deciding how to combine the grouping values *@returns {Array} Returns the arrays * of the grouped arrays@see unzip, unzipWith, zip, zipObject, zipObjectDeep, zipWith
 * @example
 *
 * zipWith([1, 2], [10, 20], [100, 200], (a, b, c) => a + b + c)
 * // => [111, 222]
 */
function zipWith(. arrays) {
  // Get the last argument iteratee and pop it out of the Arrays array
  const length = arrays.length;
  let iteratee = length > 1 ? arrays[length - 1] : undefined;
  iteratee =
    typeof iteratee === 'function' ? (arrays.pop(), iteratee) : undefined;
  // unzipWith(Arrays, iteratee)
  return unzipWith(arrays, iteratee);
}

export default zipWith;
Copy the code

zipObject

The zipObject method does something different from the Zip family. ZipObject takes two arguments, the first an array of keys, the second an array of values, and finally an object.

Here are the pre-dependent methods assignValue for ordinary assignments and baseSet for deep assignments.

baseAssignValue

__proto__ should be false as an enumerable: true. We need to rethink this.

/** * the basic implementation of 'assignValue' and 'assignMergeValue' does not check the value **@private
 * @param {Object} Object Indicates the object * to be modified@param {string} Key Specifies the attribute key * to be assigned@param {*} The value to assign */
function baseAssignValue(object, key, value) {
  // Use object.defineProperty () when assigning a value to an Object's prototype
  if (key == '__proto__') {
    Object.defineProperty(object, key, {
      configurable: true.enumerable: true.value: value,
      writable: true}); }else {
    // If it is normal, it can be allocated directlyobject[key] = value; }}export default baseAssignValue;
Copy the code

assignValue

import baseAssignValue from './baseAssignValue.js';
import eq from '.. /eq.js';

/** used to check the object's own properties */
const hasOwnProperty = Object.prototype.hasOwnProperty;

/** * Assign 'value' to 'key' on 'object' when the corresponding value is not the same as' value '@private
 * @param {Object} Object Indicates the object * to be modified@param {string} Key Specifies the attribute key * to be assigned@param {*} Value Value of the attribute to be assigned */
function assignValue(object, key, value) {
  // Get the corresponding attribute value of object
  const objValue = object[key];

  // When the key is not an object property, or the value to be assigned is not equal to the corresponding value of the objValue
  if(! (hasOwnProperty.call(object, key) && eq(objValue, value))) {// When value is a significant number
    if(value ! = =0 || 1 / value === 1 / objValue) {
      / / execution baseAssignValue ()
      baseAssignValue(object, key, value);
    }
    // Or if value is undefined and object has no key
  } else if (value === undefined && !(key in object)) {
    / / execution baseAssignValue ()baseAssignValue(object, key, value); }}export default assignValue;
Copy the code

baseSet

import assignValue from './assignValue.js';
import castPath from './castPath.js';
import isIndex from './isIndex.js';
import isObject from '.. /isObject.js';
import toKey from './toKey.js';

/** ** 'set' base implementation **@private
 * @param {Object} Object Indicates the object * to be modified@param {Array|string} Path Path of the property to be set *@param {*} Value Value of the property to be set *@param {Function} [customizer] Custom path creation method *@returns {Object} Returns the object */
function baseSet(object, path, value, customizer) {
  // If it is not an object, return it directly
  if(! isObject(object)) {return object;
  }

  // Returns an array of paths
  path = castPath(path, object);

  const length = path.length;
  const lastIndex = length - 1;

  let index = -1;
  let nested = object;

  // The path array is used to add descendant attributes to the object.
  // Object, nested, objValue are all on the original object
  // value, newValue(change) are on the property to be set
  while(nested ! =null && ++index < length) {
    const key = toKey(path[index]);
    let newValue = value;

    // How to sort the newValue of each layer
    if(index ! = lastIndex) {// Nested is the object of each layer
      const objValue = nested[key];
      // If there is a custom path to create a method, use undefined, convenient next step
      newValue = customizer ? customizer(objValue, key, nested) : undefined;
      // Start creation when there is no next level path on the object
      if (newValue === undefined) {
        newValue = isObject(objValue)
          ? // If there is a key on the original object, assign it directly to newValue
            objValue
          : // index represents an empty array, otherwise an empty object
          isIndex(path[index + 1])? [] : {}; } } assignValue(nested, key, newValue); nested = nested[key]; }return object;
}

export default baseSet;
Copy the code

baseZipObject

/** * the base implementation of zipObject that assigns attribute values using 'assignFunc'. * *@private
 * @param {Array} The props property identifier *@param {Array} Values Attribute value *@param {Function} AssignFunc is a function that assigns attributes@returns {Object} Return a new object */
function baseZipObject(props, values, assignFunc) {
  / / initialization
  let index = -1;
  const length = props.length;
  const valsLength = values.length;
  // Create a result object
  const result = {};

  / / iteration
  while (++index < length) {
    // normalize the value of values
    const value = index < valsLength ? values[index] : undefined;
    // Assign values using assignFunc
    assignFunc(result, props[index], value);
  }
  return result;
}

export default baseZipObject;
Copy the code

zipObject

When the zipObject method calls baseZipObject, it passes assignValue, so assignment is performed directly on each element of the props as a key.

import assignValue from './.internal/assignValue.js';
import baseZipObject from './.internal/baseZipObject.js';

/** * This method is similar to 'fromPairs', but it takes two arrays, * the values in the first array are the property identifiers (property names) and the values in the second array are the corresponding property values. *@since 0.4.0
 * @category Array
 * @param {Array} [props=[]] Property identifier array *@param {Array} [values=[]] Array of attribute values *@returns {Object} Return a new object *@see unzip, unzipWith, zip, zipObjectDeep, zipWith
 * @example
 *
 * zipObject(['a', 'b'], [1, 2])
 * // => { 'a': 1, 'b': 2 }
 */
function zipObject(props, values) {
  return baseZipObject(props || [], values || [], assignValue);
}

export default zipObject;
Copy the code

zipObjectDeep

The zipObjectDeep method, when calling baseZipObject, passes baseSet, so the depth assignment is based on the different form of each element of the props.

import baseSet from './.internal/baseSet.js';
import baseZipObject from './.internal/baseZipObject.js';

/** * This method is similar to 'zipObject', but it supports property paths. * *@since 4.1.0
 * @category Array
 * @param {Array} []] Attribute identifier *@param {Array} [values=[]] Attribute value *@returns {Object} Returns a new object *@see unzip, unzipWith, zip, zipObject, zipWith
 * @example
 *
 * zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2])
 * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
 */
function zipObjectDeep(props, values) {
  // baseZipObject() is also called, but the assignment method uses baseSet
  return baseZipObject(props || [], values || [], baseSet);
}

export default zipObjectDeep;
Copy the code

Front-end notepad, not regularly updated, welcome to pay attention to!

  • Wechat official account: Lin Jingyi’s notepad
  • Blog: Lin Jingyi’s notebook
  • Nuggets column: Lin Jingyi’s notebook
  • Zhihu column: Lin Jingyi’s notebook
  • Github: MageeLin