硬剛 lodash 原始碼之路,compact & concat

DIVMonster發表於2020-07-04

前置

本篇隨筆包含 _.compact_.concat 及其依賴的工具函式。

你可能需要一些 JavaScript 基礎知識才能看懂一些沒有註釋的細節。

compact

_.compact(array)

建立一個新陣列,包含原陣列中所有的非假值元素。例如 false, null, 0, "", undefined, 和 NaN 都是被認為是“假值”。

/**
 * Creates an array with all falsey values removed. The values `false`, `null`,
 * `0`, `""`, `undefined`, and `NaN` are falsey.
 *
 * @since 0.1.0
 * @category Array
 * @param {Array} array The array to compact.
 * @returns {Array} Returns the new array of filtered values.
 * @example
 *
 * compact([0, 1, false, 2, '', 3])
 * // => [1, 2, 3]
 */
function compact(array) {
  let resIndex = 0
  const result = []

  if (array == null) {
    return result
  }

  // for of 迴圈 array
  // resIndex 自增賦值符合條件的陣列
  for (const value of array) {
    if (value) {
      result[resIndex++] = value
    }
  }
  return result
}

export default compact

arrayPush

/**
 * 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`.
 */
function arrayPush(array, values) {
  var index = -1,
      length = values.length,
      offset = array.length;

  // 迴圈往array結尾追加values的元素
  while (++index < length) {
    array[offset + index] = values[index];
  }
  return array;
}

module.exports = arrayPush;

對比 v8 的 push 實現:

// Appends the arguments to the end of the array and returns the new
// length of the array. See ECMA-262, section 15.4.4.7.
function ArrayPush() {
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");

  var array = TO_OBJECT(this);
  var n = TO_LENGTH(array.length); // 被push的array的length
  var m = arguments.length;

  // Subtract n from kMaxSafeInteger rather than testing m + n >
  // kMaxSafeInteger. n may already be kMaxSafeInteger. In that case adding
  // e.g., 1 would not be safe.
  if (m > kMaxSafeInteger - n) throw %make_type_error(kPushPastSafeLength, m, n);

  for (var i = 0; i < m; i++) {
    array[i+n] = arguments[i]; // 複製元素
  }

  var new_length = n + m; // // 修正length屬性的值
  array.length = new_length;
  return new_length;
}

isObjectLike

檢查 value 是否是類物件。如果一個值不是 null,並且 typeof 結果為 object,則該值為類物件。

/**
 * Checks if `value` is object-like. A value is object-like if it's not `null`
 * and has a `typeof` result of "object".
 *
 * @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(Function)
 * // => false
 *
 * isObjectLike(null)
 * // => false
 */
function isObjectLike(value) {
  return typeof value === 'object' && value !== null
}

export default isObjectLike

isArguments

import getTag from './.internal/getTag.js' // 在上一篇文章中解釋了這個方法
import isObjectLike from './isObjectLike.js'

/**
 * Checks if `value` is likely an `arguments` object.
 *
 * @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
 */
function isArguments(value) {
  // 已經解釋過 toStringTag
  return isObjectLike(value) && getTag(value) == '[object Arguments]'
}

export default isArguments

isFlattenable

檢查 value 是否為可扁平化的 arguments 物件或陣列。

import isArguments from '../isArguments.js'

/** Built-in value reference. */
const spreadableSymbol = Symbol.isConcatSpreadable

/**
 * 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) {
  // Symbol.isConcatSpreadable 用於配置某物件作為 Array.prototype.concat() 方法的引數時是否展開其陣列元素。
  // 參考 https://segmentfault.com/q/1010000021953491 的採納答案
  return Array.isArray(value) || isArguments(value) ||
    !!(value && value[spreadableSymbol])
}

export default isFlattenable

baseFlatten

import isFlattenable from './isFlattenable.js'

/**
 * 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. // 只限於通過 "predicate"檢查的數值。
 * @param {Array} [result=[]] The initial result value. // 初始結果值
 * @returns {Array} Returns the new flattened array. // 返回新的扁平化陣列。
 */
function baseFlatten(array, depth, predicate, isStrict, result) {
  predicate || (predicate = isFlattenable)
  result || (result = [])

  if (array == null) {
    return result
  }

  for (const value of array) {
    if (depth > 0 && predicate(value)) {
      if (depth > 1) {
        // Recursively flatten arrays (susceptible to call stack limits).
        // 遞迴扁平化陣列(易受呼叫棧限制)。
        baseFlatten(value, depth - 1, predicate, isStrict, result)
      } else {
        result.push(...value)
      }
    } else if (!isStrict) {
      result[result.length] = value
    }
  }
  return result
}

export default baseFlatten

copyArray

/**
 * Copies the values of `source` to `array`.
 *
 * @private
 * @param {Array} source The array to copy values from.
 * @param {Array} [array=[]] The array to copy values to.
 * @returns {Array} Returns `array`.
 */
function copyArray(source, array) {
  let index = -1
  const length = source.length

  array || (array = new Array(length))
  while (++index < length) {
    array[index] = source[index]
  }
  return array
}

export default copyArray

isArray

/**
 * 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;

module.exports = isArray;

concat

_.concat(array, [values])

建立一個新陣列,將 array 與任何陣列或值連線在一起。

var arrayPush = require('./_arrayPush'),
    baseFlatten = require('./_baseFlatten'), // 扁平化陣列
    copyArray = require('./_copyArray'),
    isArray = require('./isArray');

/**
 * Creates a new array concatenating `array` with any additional arrays
 * and/or values.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Array
 * @param {Array} array The array to concatenate.
 * @param {...*} [values] The values to concatenate.
 * @returns {Array} Returns the new concatenated array.
 * @example
 *
 * var array = [1];
 * var other = _.concat(array, 2, [3], [[4]]);
 *
 * console.log(other);
 * // => [1, 2, 3, [4]]
 *
 * console.log(array);
 * // => [1]
 */
function concat() {
  var length = arguments.length; // 引數數量
  if (!length) {
    return [];
  }
  var args = Array(length - 1), // [empty]
      array = arguments[0],  // 原陣列
      index = length; 

  // args 加入除原陣列以外的引數其他引數的陣列或值
  while (index--) {
    args[index - 1] = arguments[index];
  }
  // 若原陣列是 Array 型別,拷貝一遍陣列返回,反之建立一個陣列
  // 將 args 扁平化並 push 到原陣列
  return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
}

module.exports = concat;

相關文章