封装数组去重的方法

ops/2024/12/15 8:56:41/

前言

之前在工作中我一直在用lodash这个方法库,前段时间又接触了更现代化的方法库radash,这两个方法库可以说是各有优劣,lodash中有很实用的cloneDeep,radash中则有tryitall等异步方法,它们都无法做到完全代替对方。因此我就萌生了自己封装的想法,我可以按照自己的习惯将lodash和radash中自己常用的一些方法复制下来,同时还可以自己封装一些它们里面所没有的方法。

这篇文章则是要介绍我封装 "数组去重" 方法的过程,我将参考 lodash 和 radash 中的实现方式,封装一个自己的去重方法。

一、我们平时如何进行数组去重?

1.双重遍历

最经典的去重方法就是双重遍历,它的思路很简单,就是通过遍历数组的方式将原数组中不重复的元素放入一个新数组中。如何判断元素是否重复呢?方法是检查新数组中是否存在该元素,如果元素是第一次出现,那么新数组中就不会存在相同的元素,如果元素不是第一次出现,新数组中就会有相同的元素,此时就是重复的情况。

javascript">const arr = [19, -2, 19, 4, 4, 0, 19, 0],
newArr = [];
let index = arr.lengthouter: while(index--){const element = arr[index]let innerIndex = newArr.lengthwhile(innerIndex--){if(element === newArr[innerIndex]){continue outer}}newArr.push(element)
}console.log(newArr);//[ 19, -2, 4, 0 ]

当然上面的实现方式非常的繁琐,我们可以使用JavaScript的数组高级方法filterindexOf来简化双重遍历的过程:

javascript">const arr = [19, -2, 19, 4, 4, 0, 19, 0];// filter + indexOf
const newArr = arr.filter((item, index, array) => index == array.indexOf(item));console.log(newArr);//[ 19, -2, 4, 0 ]

2.利用Set的特性

除了双重遍历之外,还有一种非常简单的数组去重方法,就是利用Set的特性数据不重复。先将目标数组转为Set类型,再用Array.fromSet转为数组,经过这样一次转换之后数组就实现了去重。

javascript">let arr = [19, -2, 19, 4, 4, 0, 19, 0];// set
let newArr =  Array.from(new Set(arr))console.log(newArr);//[ 19, -2, 4, 0 ]

二、lodash是如何实现数组去重的?

1.lodash中的去重方法们

lodash中有三种进行数组去重的方法,它们的具体信息见下表:

名称

评价

介绍

uniq

uniq就是纯粹的数组去重。

uniqBy

uniqBy则可以传入一个迭代函数,函数的返回值将作为判断唯一性的依据。这个方法我感觉还蛮实用的,例如说我有一个对象数组,我要去除其中id相同的对象,类似这种场景就需要它。

uniqWith

uniqWith则可以传入一个比较函数,经过我的测试比较函数会比较数组中的每一对元素,如果返回false则保留,如果返回true则删除arrVal。这个方法我感觉就有点过于自由了,感觉它已经很接近filter了。

我们打开lodash的源码就会发现,这uniquniqByuniqWith其实都来源于一个方法baseUniq

baseUniq十分复杂,下面我将尝试从其中抽丝剥茧还原出uniquniqByuniqWith三个方法。

2.实现uniq

“双轨制”结构

“双轨制”结构是指在lodash-uniq方法的实现中,是采用两种去重方法并行的策略,这两个方法是 Set去重法和双重遍历去重法。

在lodash的实现中,首先就根据需要去重的数组的长度分为了两种情况:

  • 数组长度大于等于200,使用Set去重法。
  • 数组长度小于200,使用双重遍历去重法。
javascript">function uniq(array){const LARGE_ARRAY_SIZE = 200;const {length} = array // 数组的长度if(length >= LARGE_ARRAY_SIZE){// 数组长度不小于200// Set去重法。。。}else{// 数组长度小于200// 双重遍历去重法。。。}
}

这两种方法在之前也都已经介绍过了这里就不再赘述了,我们把两种去重方法的代码填充进去:

javascript">function uniq(array){const LARGE_ARRAY_SIZE = 200;const {length} = array // 数组的长度if(length >= LARGE_ARRAY_SIZE){// 数组长度不小于200 (Set去重)const set = new Set(array);return Array.from(set);}// 数组长度小于200 (双重遍历)const result = []let index = -1outer: while (++index < length) {let value = array[index];let resultIndex = result.length;while (resultIndex--) {if (result[resultIndex] === value) {continue outer;}}result.push(value);}return result
}

lodash-uniq当中之所以要根据数组长度的不同使用不同的去重方法,我猜测应该是考虑性能才做出这种处理的:

  • 当处理一个冗长的数组时(长度大于等于200)就使用速度更快的Set方法
  • 当没有性能压力的时候(长度小于200)就使用相对较慢的双重遍历方法。

为了验证这一猜想我查阅了一些资料,找到了如下的说法:

当使用传统的去重方法时,它的时间复杂度是O(n^2),其中n是数组的长度。

当创建一个Set对象时,它会自动去除数组中的重复元素。时间复杂度接近O(n),因为Set在添加元素时的检查操作是高效的。

然后我做了一个简单的性能测试:

javascript">// 双重遍历去重
function removeDuplicates(arr) {const result = [];const { length } = arr;let index = -1;outer: while (++index < length) {let value = arr[index];let resultIndex = result.length;while (resultIndex--) {if (result[resultIndex] === value) {continue outer;}}result.push(value);}return result;
}// Set 去重
function removeDuplicatesWithSet(arr) {return Array.from(new Set(arr));
}const arr = new Array(1000000).fill(1);console.time("双重遍历方法");
removeDuplicates(arr);
console.timeEnd("双重遍历方法");
console.time("Set方法");
removeDuplicatesWithSet(arr);
console.timeEnd("Set方法");

实现的结果如下:

数组长度

双重遍历法的耗时

Set去重法的耗时

一百万

4.285 ms

6.094 ms

十万

0.708 ms

0.670 ms

一万

0.501 ms

0.153 ms

通过这个实验我得出了以下的结论:

当数组长度为一百万时,双重遍历方法明显要比Set方法更快;当数组长度为十万时,两者的速度接近,Set方法略微快于双重遍历法;当数组长度为一万时,Set方法明显更快。所以当数组长度在十万以内时,Set方法会有明显的性能优势。

“相等性”问题

在搞清楚了“双轨制”结构之后,其实我们就已经复现了lodash-uniq方法了,但是在源码中还有一个细节另我感到疑惑,就是下面这个东西:

这里的computed就是第一次遍历数组时获取到的数组元素value(即,待比较的元素),所以computed === computed就相当于是 value === valueif(value === value)就是在判断元素是否自己等于自己。

所以说源码中的双重遍历法部分的代码比我上面写的还要更加复杂。参考下面的代码,在外层的循环outer中,并没有立即进行内层循环,而是对数组中的元素进行了一个自相等的判断。若元素value全等于它自身,则通过内层的while循环查询结果数组中是否存在该元素,若元素value不等于它自身,则使用Array.includes()方法判断结果数组中是否存在该元素。

javascript">outer: while (++index < length) {let value = array[index];if (value === value) {let resultIndex = result.length;while (resultIndex--) {if (result[resultIndex] === value) {continue outer;}}result.push(value);} else if (!result.includes(value)) {result.push(value);}
}

这个最初value === value的写法令我百思不得其解,我一度以为lodash的开发者是不是闲的蛋疼,写这种无效的代码。

后来我突然想到一个问题,有没有可能在某些情况下value不等于自身呢?我模糊的记得以前我在看红宝书的时候有看过相关的内容,于是我回去找了一下,原文如下:

没错,很显然value === value正是为了处理那些“即使===操作符也无能为力的特殊情况”,并且红宝书中还给我们提供了更加现代化的判断相等性方法Object.is()

因此倘若不考虑兼容性的话,我们完全可以将双重遍历部分的代码改写为如下的形式:

javascript">outer: while (++index < length) {let value = array[index];let resultIndex = result.length;while (resultIndex--) {if (Object.is(result[resultIndex],value)) {continue outer;}}result.push(value);
}

所以最终完整的lodash-uniq方法的代码如下:

javascript">function uniq(array) {const result = [];const { length } = array;const LARGE_ARRAY_SIZE = 200;if (length >= LARGE_ARRAY_SIZE) {const set = new Set(array);return Array.from(set);}let index = -1;outer: while (++index < length) {let value = array[index];if (value === value) {let resultIndex = result.length;while (resultIndex--) {if (result[resultIndex] === value) {continue outer;}}result.push(value);} else if (!result.includes(value)) {result.push(value);}}return result;
}

3.实现uniqBy

lodash-uniqBy方法可以支持在去重的过程中不使用数组元素的原始值进行相等性比较,而是使用原始值的一个映射值进行比较。映射值通过传入的映射函数获得。我把这种功能称为“映射去重”。

这可以大大的增强去重的功能,例如有如下的对象数组:

javascript">const arr = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 2 }, { id: 1 }];

我们希望根据其id值进行去重,以保证对象id唯一。此时就可以使用lodash-uniqBy方法实现:

javascript">console.log(_.uniqBy(arr, item => item.id)); // [{ id: 1 }, { id: 2 }, { id: 3 }]

实现“映射去重”

这里我首先要探讨一个问题:“双重遍历法" 和 "Set去重法" 是否可以扩展映射去重的功能?

很显然“双重遍历法”肯定是可以的,但“Set去重法”是不行的。因为“Set去重法”是直接利用Set的特性实现去重的,没有拓展的空间。所以下面我就将以“双重遍历法”为基础实现“映射去重”。

“双重遍历法”原本的基于下面的逻辑实现去重的:

"双重遍历法" 的核心其实是上面提到的 " 唯一数据池 " ,顾名思义“唯一数据池”中存储的都是不重复的唯一数据。以下面的代码为例,“唯一数据池”其实就是result数组。

在实现去重操作的过程中“唯一数据池”实际上具有双重身份,它既是“检查器”又是“成果集”。说它是“检查器”是因为我们需要利用“唯一数据池”来核验数组元素是否重复。说它是“成果集”是因为我们会将不重复的数组元素存储到“唯一数据池”中,最终“唯一数据池”会成为我们期望的“成果集”(即去重后的数组)。

javascript">  // 双重遍历去重function removeDuplicates(arr, mapper) {const result = []; //唯一数据池const { length } = arr;let index = -1;outer: while (++index < length) {// 数组的元素let value = arr[index];let rIndex = result.length;while (rIndex--) {if (result[rIndex] === value) {continue outer;}}result.push(value);}return result;}

实现“映射去重”的关键就是要将“检查器”的身份从“唯一数据池”中剥离出来。我会创建一个新的数组seen作为“检查器”,它用来存储数组元素的映射值(因为现在要基于映射值判断是否重复),我将其称为“检查数据池”,原来的“唯一数据池”只作为“成果集”,我将其改名为“成果数据池”,这两个数据池中的数据都是唯一的。

此时“映射去重”的逻辑就如下图所示:

实现代码如下:

javascript">  // 映射去重function removeDuplicates(arr, mapper) {const result = []; //成果数据池const seen = []; //检查数据池const { length } = arr;let index = -1;outer: while (++index < length) {// 数组的元素let value = arr[index];// 元素的映射值let computed = mapper(value);let seenIndex = seen.length;while (seenIndex--) {if (seen[seenIndex] === computed) {continue outer;}}seen.push(computed);result.push(value);}return result;}

新“双轨制”结构

接下来就可以开始着手实现lodash-uniqBy方法了,但是这里就有一个问题,lodash中的去重方法是秉持着“双轨制”结构的,使用了“双重遍历法”和“Set去重法”两种方式去重。“双重遍历法”我们已经在上一个部分实现了映射去重的功能,但“Set去重法”该怎么改呢?

lodash给出的解决思路是,放弃“Set去重法”,而是使用Set数据结构去增强“双重遍历法”,简单来说就是将 "检查数据池" 由原来的数组类型改为Set数据类型。这种方法本质上还是利用Set来提升性能,我将其称之为“Set双重遍历法”,这样我们就可以构建一个新的“双轨制”结构:

javascript">function uniqBy(array,iteratee){const LARGE_ARRAY_SIZE = 200;const {length} = array // 数组的长度const result = []const seen = []if(length >= LARGE_ARRAY_SIZE){// 数组长度不小于200 (Set双重遍历)outer: while (++index < length) {// 数组的元素let value = arr[index];// 元素的映射值let computed = iteratee(value);let seenIndex = seen.length;if (!seen.has(computed)) {seen.add(computed);result.push(value);}}return result; }// 数组长度小于200 (普通双重遍历)let index = -1outer: while (++index < length) {    let value = array[index]; // 数组元素let computed = iteratee(value); // 元素的映射值if (value === value) { let seenIndex = seen.length;while (seenIndex--) {if (seen[seenIndex] === computed) {continue outer;}}seen.push(computed);result.push(value);} else if (!seen.includes(computed)) {seen.push(computed);result.push(value);}}return result
}

当然上面的写法有重复的部分,我们将重复的部分合并,来给其进行一下“瘦身”。合并之后我们成功复现了lodash-uniqBy方法了:

javascript">function uniqBy(array, iteratee) {const LARGE_ARRAY_SIZE = 200;const result = [];let seen = [];let isCommon = true;const { length } = array;let index = -1;if (length >= LARGE_ARRAY_SIZE) {seen = new Set();seen.prototype.push = seen.prototype.add;seen.prototype.includes = seen.prototype.has;isCommon = false;}outer: while (++index < length) {let value = array[index];let computed = iteratee(value);if (isCommon && computed === computed) {let seenIndex = seen.length;while (seenIndex--) {if (seen[seenIndex] === computed) {continue outer;}}seen.push(computed);result.push(value);} else if (!seen.includes(computed)) {seen.push(computed);result.push(value);}}return result;
}

4.实现uniqWith

lodash-uniqWith是lodash中的第三种数组去重方法,它支持使用者自定义去重时的比较规则。

例如存在如下的数组,我希望在去重的时候 1和2也算作重复值。

javascript">const arr = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5];

我们可以借助lodash的uniqWith()方法实现上述的功能:

javascript">const newArr = _.uniqWith(arr,(arrVal, othVal) =>arrVal === othVal ||(arrVal === 1 && othVal === 2) ||(arrVal === 2 && othVal === 1)
);console.log(newArr);// [1, 3, 4, 5]

比较函数介绍

lodash-uniqWith 的比较函数其实类似于 sort的 排序函数,都是取数组中的两个元素进行比较,只不过uniqWith 的比较函数用来判断两个元素的相等性,sort的 排序函数则用来判断两个函数的排列顺序。调用比较函数的过程在了解了lodash去重的原理后也不难推测,应该就是遍历需要去重的数组,将元素作为比较函数的参数arrVal,与结果数组中的元素进行比较,结果数组中的元素就作为参数othVal,从而实现去重效果。

uniqWith的基本原理

lodash-uniqWith的原理非常简单,就是使用比较函数替代原来用于比较相等性的代码。原来我们是用下面的这些方式比较数组元素是否相等的,现在只需要使用比较函数就可以了。

javascript">result[rIndex] === value   //原始方法
Object.is(value,result[rIndex])  //解决"自相等"问题
result.includes(value)  //解决"自相等"问题
result.has(value)  // 用Set作为 "检查数据池"comparator(value,result[rIndex]) //使用比较函数

放弃双轨制结构

lodash-uniqWith的实现中放弃了传统的“双轨制”架构,不再使用Set去优化性能,原因也是因为Set是没有办法兼容自定义的比较函数的 (除非是使用forEach方法遍历Set数据,但是这样就完全没有性能优势了)。另一方面 对“相等性问题”的处理也被抛弃了,因为现在使用的是外部传入的比较函数,这种情况下“相等性问题”就是使用者需要去考虑的事情了。

最终的实现如下:

javascript">function uniqWith(array, comparator) {const result = [];const { length } = array;let index = -1;outer: while (++index < length) {let value = array[index];let resultIndex = result.length;while (resultIndex--) {if (comparator(value, result[resultIndex])) {continue outer;}}result.push(value);}return result;
}

三、Radash的数组去重

在Radash中用于进行数组去重的是unique方法,我之前就听闻这个方法有一个大问题,就是去重之后无法保证数组元素的顺序,不知道是不是因为这个原因在Radash的官方文档中都没有关于unique方法的介绍。

所幸的是源码当中还保留了unique,可以让我们窥其全貌。我们先看一下unique方法的功能,除了基本的去重功能外,还有一个可选参数toKeytoKey其实就是映射函数可以实现映射去重。

Radash的代码跟lodash就不一样了,可读性非常高,所以我们也无需像之前那样费劲心机的去复现代码了,我只需要简单的将代码转换成JS语法(原来是TS):

javascript">function unique(array, toKey) {const valueMap = array.reduce((acc, item) => {const key = toKey ? toKey(item) : item;if (acc[key]) return acc;acc[key] = item;return acc;}, {});return Object.values(valueMap);
}

unique如何实现映射去重?

unique实现映射去重的方式十分有趣,它实现方式其实类似于uniqBy中当数组长度大于200时使用的“Set双重遍历去重”。

但是unique有一个十分精巧的设计,那就是它的“数据池”。uniqBy在实现映射去重时使用了两个数组(或者Set)分别作为“检查数据池”和“成果数据池”。unique原理相同,也是有两个数据池,但是它是将对象中的key作为了“检查数据池”,对象的value作为了 "成果数据池" , 这种设计大大的提升了代码的简洁性。

但是这种简洁性也是有代价的,代价就是我之前提到的问题“进行去重之后无法保证数组元素的顺序”。原因也显而易见,unique中使用对象来保存“成果”,而对象中属性并不是按照传入顺序进行排序的,所以就会出现去重之后的数组其中元素的顺序乱了的情况。

四、实现我的数组去重方法

lodash和Radash中数组去重方法各有各的特点,lodash大而全,考虑了兼容性、扩展性、性能等多个方面,而Radash则是小而精,代码简洁可读性强。相对来说我更加喜欢Radash的理念(因为我封装的是给自己一个人用的方法),但是Radash-unique方法存在的 "顺序问题" 又是我无法接受的,所以我打算以参考两方的实现来完成我的数组去重。

1.参数与功能选择

数组去重的方法大致有三种基础功能,分别是:

  • 基本去重功能 , 对应lodash的uniq方法
  • 映射去重功能 , 对应lodash的uniqBy方法
  • 自定义比较规则功能,对应lodash的uniqWith方法

在我实际的使用过程中我就发现其实uniqBy方法和uniqWith方法它们的功能在很多时候是重合的。例如下面这种情况:

我想要基于对象的id进行数组去重,实际上使用uniqByuniqWith都可以实现。

javascript">const objArr = [{ id: 1 }, { id: 2 }, { id: 1 }, { id: 3 }, { id: 2 }];
console.log(uniqBy(objArr, item => item.id)); //[ { id: 1 }, { id: 2 }, { id: 3 } ]
console.log(uniqWith(objArr, (a , b) => a.id === b.id)); //[ { id: 1 }, { id: 2 }, { id: 3 } ]

因此为了避免功能上的冗余,我选择和radash中一样只实现“基本去重”和“映射去重”这两个功能。所以我的方法就设置arrayiteratee两个参数。

javascript">/**** @param {Array} array 需要去重的数组* @param {function} iteratee 映射函数* @returns {Array} 去重后的数组*/
function unique(array, iteratee) {//参数处理if (!array || !array.length) return [];iteratee = typeof iteratee !== "function" ? item => item : iteratee;const comparator = (a, b) => Object.is(a, b); 
}

2.实现去重功能

我写了两版的数组去重方法,其中版本一是仿照lodash实现的数组去重,版本二则是仿照radash实现的数组去重。

版本一 仿照lodash实现的数组去重

这个版本的基本思路与lodash相同,但是为了精简代码,我放弃了lodash中经典的“双轨制”结构,没有使用Set去优化去重时的性能,只保留了双重遍历去重。

javascript">/**** @param {Array} array 需要去重的数组* @param {function} iteratee 映射函数* @returns {Array} 去重后的数组*/
function unique(array, iteratee) {//参数处理if (!array || !array.length) return [];iteratee = typeof iteratee !== "function" ? item => item : iteratee;const comparator = (a, b) => Object.is(a, b);//去重const { length } = array;let index = -1;const result = [];const seen = [];outer: while (++index < length) {const value = array[index];const computed = iteratee(value);let i = seen.length;while (i--) {if (comparator(computed, seen[i])) {continue outer;}}seen.push(computed);result.push(value);}return result;
}

版本二 仿照radash实现的数组去重

这个版本则与radash中的方法几乎一样,唯一的区别在于我替换了“数据池”。radash中使用一个对象作为“数据池”,这导致了其输出的结果数组中元素的顺序乱了。为了解决这一问题,我将“数据池”由对象更改为了键值对数组,这样就可以保证输出结果的顺序了。

javascript">/**** @param {Array} array 需要去重的数组* @param {function} iteratee 映射函数* @returns {Array} 去重后的数组*/
function unique(array, iteratee) {//参数处理if (!array || !array.length) return [];iteratee = typeof iteratee !== "function" ? item => item : iteratee;const comparator = (a, b) => Object.is(a, b);//去重const valueMap = array.reduce((acc, el) => {const computed = iteratee(el);if (!acc.find(item => comparator(computed, item[0]))) {acc.push([computed, el]);}return acc;}, []);return valueMap.map(item => item[1]);
}

3.实现深度比较

引用数据类型的相等性

“相等性”问题我们在上面研究过,但是还有一种情况是没有考虑到的,那就是引用数据类型的相等性。

下面的这种情况是符合JS的语法的,虽然两个对象看起来一模一样,但是因为它们的引用不同所以呈现的结果是它们不相等。

javascript">console.log({} === {})//false

其它的一些数据结构也会这样:

javascript">console.log({} === {});//false
console.log([] === []);//false
console.log(new Set === new Set);//false
console.log(new Map === new Map);//false
console.log(/123/ === /123/)//false

这种结果符合JS语法,但并不符合实际的需求,我们在去重的时候是需要根据值来判断是否相等。

lodash中的解决方案

针对这种情况lodash是给出了解决方案的,借助uniqWith+ isEqual的组合可以实现比较两个引用数据类型的值是否相等。

集成深度比较功能

我参考lodash和radash中isEqual方法的实现方式,编写了自己的比较方法,具体内容可以参考下面这篇文章:封装数据比较的方法-CSDN博客

我基于isEqual方法实现深度比较功能。

javascript">/**** @param {Array} array 需要去重的数组* @param {function} iteratee 映射函数* @param {boolean} deepCompare 是否深度比较* @returns {Array} 去重后的数组*/
function unique(array, iteratee, deepCompare ) {//参数处理if (!array || !array.length) return [];iteratee = typeof iteratee !== "function" ? item => item : iteratee;const comparator = deepCompare ? isEqual : (a, b) => Object.is(a, b);//去重const { length } = array;let index = -1;const result = [];const seen = [];outer: while (++index < length) {const value = array[index];const computed = iteratee(value);let i = seen.length;while (i--) {if (comparator(computed, seen[i])) {continue outer;}}seen.push(computed);result.push(value);}return result;
}
javascript">import { isEqual } from "../../../utils/func.js";
/**** @param {Array} array 需要去重的数组* @param {function} iteratee 映射函数* @param {boolean} deepCompare 是否深度比较* @returns {Array} 去重后的数组*/
function unique(array, iteratee, deepCompare) {//参数处理if (!array || !array.length) return [];iteratee = typeof iteratee !== "function" ? item => item : iteratee;const comparator = deepCompare ? isEqual : (a, b) => Object.is(a, b);//去重const valueMap = array.reduce((acc, el) => {const computed = iteratee(el);if (!acc.find(item => comparator(computed, item[0]))) {acc.push([computed, el]);}return acc;}, []);return valueMap.map(item => item[1]);
}

4.增强iteratee参数

lodash中的iteratee参数

我在介绍分析lodash的uniqBy方法的时候其实漏掉了一个功能,它的参数iteratee不仅仅可以是一个函数,也可以是以下的其它类型。

因此uniqBy还可以这样使用:

javascript">uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
// [{ 'x': 1 }, { 'x': 2 }]

get方法

我看了一下uniqByiteratee参数的处理方式,其内容十分复杂一些地方我也没有办法完全搞懂。但是我发现iteratee参数的功能与lodash的get方法似乎有些关联。

lodash中的get方法可以依照一个路径(路径可以是字符串或者数组)从字符串中获取数据,可以实现如下的效果:

因此在我看来就可以这样基于get方法实现对iteratee的增强:

javascript">uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x')
// => 相当于
uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], (item) => get(item,'x'))

实现iteratee参数的增强

我参考lodash和radash中get方法的实现方式,编写了自己的get方法,具体内容可以参考下面这篇文章:

封装对象属性值获取方法-CSDN博客

我基于get方法增强了iteratee参数的功能:

javascript">import { isEqual, get } from "../../../utils/func.js";
/**** @param {Array} array 需要去重的数组* @param {function|string|Array} getValue 映射函数或者是要获取的值在对象中的路径* @param {Object} options 其它选项* @param {boolean} options.deepCompare 是否深度比较* @returns {Array} 去重后的数组*/
function unique(array, getValue, options = {}) {//参数处理if (!array || !array.length) return [];const iteratee =typeof getValue === "function"? getValue: typeof getValue === "string" || Array.isArray(getValue)? item => get(item, getValue): item => item;const comparator = options.deepCompare ? isEqual : (a, b) => Object.is(a, b);//去重const { length } = array;let index = -1;const result = [];const seen = [];outer: while (++index < length) {const value = array[index];const computed = iteratee(value);let i = seen.length;while (i--) {if (comparator(computed, seen[i])) {continue outer;}}seen.push(computed);result.push(value);}return result;
}
javascript">import { isEqual, get } from "../../../utils/func.js";
/**** @param {Array} array 需要去重的数组* @param {function|string|Array} getValue 映射函数或者是要获取的值在对象中的路径* @param {Object} options 其它选项* @param {boolean} options.deepCompare 是否深度比较* @returns {Array} 去重后的数组*/
function unique(array, getValue, options = {}) {//参数处理if (!array || !array.length) return [];const iteratee =typeof getValue === "function"? getValue: typeof getValue === "string" || Array.isArray(getValue)? item => get(item, getValue): item => item;const comparator = options.deepCompare ? isEqual : (a, b) => Object.is(a, b);//去重const valueMap = array.reduce((acc, el) => {const computed = iteratee(el);if (!acc.find(item => comparator(computed, item[0]))) {acc.push([computed, el]);}return acc;}, []);return valueMap.map(item => item[1]);
}

参考资料

  1. Lodash中文文档
  2. Radash中文文档
  3. 数组去重:lodash里的uniq实现
  4. 《JavaScript 高级程序设计》第八章 对象类与面向对象编程


http://www.ppmy.cn/ops/142053.html

相关文章

预言机调研

预言机 1. 概述 预言机主要承担两个工作&#xff0c;一是验证信息可靠性&#xff0c;二是传递信息。 如果没有预言机&#xff0c;区块链的信息来源将仅限于其内部数据&#xff0c;其广泛使用的潜力和可能性将会大大降低。 区块链预言机是区块链与外部世界之间的桥梁。它们使区…

使用ElasticSearch实现全文检索

文章目录 全文检索任务描述技术难点任务目标实现过程1. java读取Json文件&#xff0c;并导入MySQL数据库中2. 利用Logstah完成MySQL到ES的数据同步3. 开始编写功能接口3.1 全文检索接口3.2 查询详情 4. 前端调用 全文检索 任务描述 在获取到数据之后如何在ES中进行数据建模&a…

实现盘盈单自动化处理:吉客云与金蝶云星空数据对接

盘盈单103v2对接其他入库&#xff1a;吉客云数据集成到金蝶云星空 在企业信息化管理中&#xff0c;数据的高效流转和准确性至关重要。本文将分享一个实际案例&#xff0c;展示如何通过轻易云数据集成平台&#xff0c;将吉客云的数据无缝对接到金蝶云星空&#xff0c;实现盘盈单…

基础开发工具-编辑器vim

vim操作键盘图 下图是比较基础的vim操作键盘图 &#xff08;IDE例子&#xff09; vi/vim的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是vim是vi的升级版本&#xff0c;它不仅兼容vi的所有指令&#xff0c;⽽且还有⼀些新的特性在⾥⾯。例如语法加亮&a…

Java web - 后端开发

一 Maven Maven是apache旗下的一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。 Maven的作用

windows C#-实现具有自动实现属性的轻型类

下面演示如何创建一个不可变的轻型类&#xff0c;该类仅用于封装一组自动实现的属性。 当你必须使用引用类型语义时&#xff0c;请使用此种构造而不是结构。 可通过以下方法来实现不可变的属性&#xff1a; 仅声明 get 访问器&#xff0c;使属性除了能在该类型的构造函数中可…

ubuntu20.04复现 Leg-KILO

这里写目录标题 opencv版本问题下载3.2.0源代码进入解压后的目录创建构建目录运行 CMake 配置 配置时指定一个独立的安装目录&#xff0c;例如 /opt/opencv-3.2&#xff1a;出错&#xff1a; 使用多线程编译错误1&#xff1a; stdlib.h: 没有那个文件或目录错误2&#xff1a;er…

学习maven(添加依赖坐标,maven的常用命令,依赖传递,解决依赖冲突)

目录 前言 添加依赖坐标 maven 的常用命令 如下图所示&#xff1a;重点是标红的 如何使用这些maven的常用命令呢&#xff1f; 实例 maven常用的命令可以在IDEA中有自带插件来完成 打开IDEA的命令行终端 依赖传递 什么是依赖传递呢&#xff1f; 解决依赖冲突问题 什么…