lodash-intersectionWith源码研读解析

706 阅读3分钟

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

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

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

一、环境准备

  • lodash 版本 v4.0.0

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

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

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

cd axios

npm install

npm run test

二、结构分析

2e11248616457233cdf4ac078228c5a.png

  这是一张 intersectionWith 依赖引用路径图,相对复杂一些,按照功能划分,大致包括baseIntersection 模块、castArrayLikeObject模块,与lodash-intersectionBy源码研读解析相同,现在不再赘述,仅对 intersectionWith 主体及其测试用例做一下说明。

三、函数研读

intersectionWith 模块

这个方法类似_.intersection,区别是它接受一个 comparator 调用比较arrays中的元素。结果值是从第一数组中选择。comparator 会传入两个参数:(arrVal, othVal)

import map from './map.js'
import baseIntersection from './.internal/baseIntersection.js'
import castArrayLikeObject from './.internal/castArrayLikeObject.js'
import last from './last.js'

/**
 * @since 4.0.0
 * @category Array
 * @param {...Array} [arrays] 待检查的数组
 * @param {Function} [comparator] comparator(比较器)调用每个元素
 * @returns {Array} 返回一个包含所有传入数组交集元素的新数组
 * @example
 *
 * const objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
 * const others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]
 *
 * intersectionWith(objects, others, isEqual)
 * // => [{ 'x': 1, 'y': 2 }]
 */
function intersectionWith(...arrays) {
  let comparator = last(arrays)
  const mapped = map(arrays, castArrayLikeObject)

  comparator = typeof comparator === 'function' ? comparator : undefined
  if (comparator) {
    mapped.pop()
  }
  return (mapped.length && mapped[0] === arrays[0])
    ? baseIntersection(mapped, undefined, comparator)
    : []
}

export default intersectionWith

  • intersectionBy 不同的是 intersectionWith 最后一位是传入的比较器 comparator,同样是使用 last 获取 comparator
  • 使用 map 配合迭代器 castArrayLikeObject 检测入参 arrays 是否是合法数组,返回 mapped
  • 使用 typeof 判断为 comparator 类型,如果是 function 则表明最后一项为合法比较器,否则比较器取值 undefined
  • mapped 存在并且入参第一项 arrays 是合法数组,则调用 baseIntersection 检测公共元素,否则返回空数组

四、单元测试

测试用例代码如下:

import assert from 'assert';
import lodashStable from 'lodash';
import { LARGE_ARRAY_SIZE, stubZero } from './utils.js';
import intersectionWith from '../intersectionWith.js';

describe('intersectionWith', function() {
  it('should work with a `comparator`', function() {
    var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }],
        others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }],
        actual = intersectionWith(objects, others, lodashStable.isEqual);

    assert.deepStrictEqual(actual, [objects[0]]);
  });


  it('should preserve the sign of `0`', function() {
    var array = [-0],
        largeArray = lodashStable.times(LARGE_ARRAY_SIZE, stubZero),
        others = [[0], largeArray],
        expected = lodashStable.map(others, lodashStable.constant(['-0']));

    var actual = lodashStable.map(others, function(other) {
      return lodashStable.map(intersectionWith(array, other, lodashStable.eq), lodashStable.toString);
    });

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

共2个测试点,分别是

  • 应该和比较器 comparator 一起工作
  • 应该保留 0 的符号

第一个测试用例很简单,通过传入一个比较器 isEqual 测试其是否可执行,看工具函数是否满足基本要求 - 接受一个 comparator 调用比较arrays中的元素

第二个测试用中通过 stubZero = function() { return 0; },LARGE_ARRAY_SIZE = 200 构造了一个包含两百个 0largeArray,比较边界和 [-0] 是否满足工具函数是否满足基本要求 - 可以从 [0, 0, 0 ...] 中 筛选出 -0

修改一下 scripts 命令 "test": "mocha -r esm test/*.test.js" ==> "test": "mocha -r esm test/intersectionWith.test.js",只运行 intersectionWith 的单元测试文件,运行结果如下

image.png