前言
之前在工作中我一直在用lodash这个方法库,前段时间又接触了更现代化的方法库radash,这两个方法库可以说是各有优劣,lodash中有很实用的cloneDeep
,radash中则有tryit
、all
等异步方法,它们都无法做到完全代替对方。因此我就萌生了自己封装的想法,我可以按照自己的习惯将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的数组高级方法filter
和indexOf
来简化双重遍历的过程:
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.from
将Set
转为数组,经过这样一次转换之后数组就实现了去重。
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 |
| |
uniqBy |
| |
uniqWith |
|
我们打开lodash的源码就会发现,这uniq
、uniqBy
、uniqWith
其实都来源于一个方法baseUniq
。
baseUniq
十分复杂,下面我将尝试从其中抽丝剥茧还原出uniq
、uniqBy
、uniqWith
三个方法。
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 === value
,if(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
方法的功能,除了基本的去重功能外,还有一个可选参数toKey
,toKey
其实就是映射函数可以实现映射去重。
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进行数组去重,实际上使用uniqBy
和uniqWith
都可以实现。
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中一样只实现“基本去重”和“映射去重”这两个功能。所以我的方法就设置array
和iteratee
两个参数。
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方法
我看了一下uniqBy
中iteratee
参数的处理方式,其内容十分复杂一些地方我也没有办法完全搞懂。但是我发现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]);
}
参考资料
- Lodash中文文档
- Radash中文文档
- 数组去重:lodash里的uniq实现
- 《JavaScript 高级程序设计》第八章 对象类与面向对象编程