【lodash】initial源码研读解析

225 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

A modern JavaScript utility library delivering modularity, performance & extras.

lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库

一、环境准备

  • lodash 版本 v4.0.0

  • 通过 github1s 网页可以 查看 lodash - initial 源码

  • 调试测试用例可以 clone 到本地

git clone https://github.com/lodash/lodash.git

cd axios


npm install

npm run test

二、结构分析

8c3aec8ed04f347b05c68089c29bbf2.png

  这是一张 initial 依赖引用路径图,按照功能划分,包含sliceinitial

三、函数研读

裁剪数组array,从 start 位置开始到end结束,但不包括 end 本身的位置

1. slice 模块

/**
 * @since 3.0.0
 * @category Array
 * @param {Array} 要裁剪数组
 * @param {number} 开始位置
 * @param {number} 结束位置
 * @returns {Array} 返回 数组`array` 裁剪部分的新数组
 * @example
 *
 * var array = [1, 2, 3, 4]
 *
 * _.slice(array, 2)
 * // => [3, 4]
 */
function slice(array, start, end) {
    let length = array == null ? 0 : array.length;
    if (!length) {
        return [];
    }
    start = start == null ? 0 : start;
    end = end === undefined ? length : end;

    if (start < 0) {
        start = -start > length ? 0 : length + start;
    }
    end = end > length ? length : end;
    if (end < 0) {
        end += length;
    }
    length = start > end ? 0 : (end - start) >>> 0;
    start >>>= 0;

    let index = -1;
    const result = new Array(length);
    while (++index < length) {
        result[index] = array[index + start];
    }
    return result;
}

export default slice;
  • 如果入参 arraynull 或者空数组则直接返 []
  • 如果开始位置 start 未定义则默认为 0,结束位置 end 未定义则默认为 arraylength
  • 如果开始位置 start 小于 0 则为负索引,将被视为与 array 末尾位置的偏移量,需要注意的是如果偏移量大于 array 长度则默认为 0,结束位置 end 同样处理
  • 根据 start 与 end 计算返回区间,其中 >>> 0 确保了 start 和 length 落在 js 双精度有效表达范围【0 ~ 0xFFFFFFFF】中,详情可以查看js 中表达式 >>> 0 浅析
  • 最后使用 new Array(length)重新创建一个 result 数组并逐一赋值后返回

2. initial 模块

获取数组array中除了最后一个元素之外的所有元素

import slice from './slice.js'

/**
 * @since 0.1.0
 * @category Array
 * @param {Array} array 要查询的数组
 * @returns {Array} 返回截取后的数组array
 * @example
 *
 * initial([1, 2, 3])
 * // => [1, 2]
 */
function initial(array) {
  const length = array == null ? 0 : array.length
  return length ? slice(array, 0, -1) : []
}

export default initial

  • 如果参数 array 不为 null 并且有 length 属性,则认为其为 Array 类型,否则返回空数组
  • 如果入参 arrayArray 类型,则进一步调用 slice 获取 array[0,array.length - 2] 范围的内容,否则直接返回空数组

3.测试用例

import assert from 'assert';
import lodashStable from 'lodash';
import { falsey, stubArray, LARGE_ARRAY_SIZE } from './utils.js';
import initial from '../initial.js';

describe('initial', function() {
  var array = [1, 2, 3];

  it('should accept a falsey `array`', function() {
    var expected = lodashStable.map(falsey, stubArray);

    var actual = lodashStable.map(falsey, function(array, index) {
      try {
        return index ? initial(array) : initial();
      } catch (e) {}
    });

    assert.deepStrictEqual(actual, expected);
  });

  it('should exclude last element', function() {
    assert.deepStrictEqual(initial(array), [1, 2]);
  });

  it('should return an empty when querying empty arrays', function() {
    assert.deepStrictEqual(initial([]), []);
  });

  it('should work as an iteratee for methods like `_.map`', function() {
    var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
        actual = lodashStable.map(array, initial);

    assert.deepStrictEqual(actual, [[1, 2], [4, 5], [7, 8]]);
  });

  it('should work in a lazy sequence', function() {
    var array = lodashStable.range(LARGE_ARRAY_SIZE),
        values = [];

    var actual = _(array).initial().filter(function(value) {
      values.push(value);
      return false;
    })
    .value();

    assert.deepEqual(actual, []);
    assert.deepEqual(values, initial(array));

    values = [];

    actual = _(array).filter(function(value) {
      values.push(value);
      return isEven(value);
    })
    .initial()
    .value();

    assert.deepEqual(actual, initial(lodashStable.filter(array, isEven)));
    assert.deepEqual(values, array);
  });
});
  • 可以接受一个假值 array 作为入参,其中 stubArray = function() { return []; },
  • 应该排除掉最后一项
  • 入参为空数组时应该返回空数组
  • 可以像 _.map 一样对入参的数组元素每一项迭代
  • 应该符合惰性运算要求

所谓像类 _.map 一样的方法作为数组迭代器入参,是指入参数组的每一项如果也是数组则其会作为入参再次被 head 递归调用,详情可以查看关于 map 的源码解析,这里不做额外说明。 image.png

的惰性运算可以参考lodash - first源码研读解析