数组去重, 一般都是在面试的时候才会碰到, 一般是要求手写数组去重方法的代码. 如果是被提问到, 数组去重的方法有哪些? 你能答出其中的 10 种, 面试官很有可能对你刮目相看.
在真实的项目中碰到的数组去重, 一般都是后台去处理, 很少让前端处理数组去重. 虽然日常项目用到的概率比较低, 但还是需要了解一下, 以防面试的时候可能回被问到.
本文是基于 JavaScript 数组去重(12 种方法, 史上最全) 的代码而进行的重度测试, 并加入自己的理解, 如有不对之处, 请不吝赐教.
测试数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 var arr = [ 0 , 0 , 1 , 1 , 'true' , 'true' , true , true , 'false' , 'false' , false , false , 'undefined' , 'undefined' , undefined , undefined , 'null' , 'null' , null , null , 'NaN' , 'NaN' , NaN , NaN , {}, {}, { a: 1 }, { b: 2 }, { a: 1 , b: 2 }, [1 , 2 , 3 ], [1 , 2 , 3 ], [11 , 2 , 3 ], [1 , 22 , 3 ], function fun ( ) {}, function fun ( ) {}, function f ( ) {}, function fun ( ) { return 1 }, function fun ( ) { return 2 } ]
一、利用 ES6 Set 去重(ES6 中最常用) 1 2 3 4 5 6 function unique (arr ) { return Array .from(new Set (arr)) } console .log(unique(arr))
不考虑兼容性, 这种去重的方法代码最少. 但这种方法无法 去掉对象 .
二、利用 for 嵌套 for, 然后 splice 去重(ES5 中最常用) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function unique (arr ) { for (var i = 0 ; i < arr.length; i++) { for (var j = i + 1 ; j < arr.length; j++) { if (arr[i] === arr[j]) { arr.splice(j, 1 ) j-- } } } return arr } console .log(unique(arr))function unique (arr ) { for (var i = 0 ; i < arr.length; i++) { for (var j = i + 1 ; j < arr.length; j++) { if (arr[i] == arr[j]) { arr.splice(j, 1 ) j-- } } } return arr } console .log(unique(arr))
双层循环, 外层循环元素, 内层循环时比较值. 值相同时, 则删去这个值.
NaN 和 对象没有去重
三、利用 indexOf 去重 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function unique (arr ) { if (!Array .isArray(arr)) { console .log('type error!' ) return } var array = [] for (var i = 0 ; i < arr.length; i++) { if (array.indexOf(arr[i]) === -1 ) { array.push(arr[i]) } } return array } console .log(unique(arr))
新建一个空的结果数组, for 循环原数组, 判断结果数组是否存在当前元素, 如果有相同的值则跳过, 不相同则 push 进数组.
在评论中看到下面代码, 感觉思路不错
if(s.indexOf(s[i]) !== s.lastIndexOf(s[i]) && res.indexOf(s[i]) === -1) res.push(s[i])
NaN 和 对象没有去重
四、利用 sort() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function unique (arr ) { if (!Array .isArray(arr)) { console .log('type error!' ) return } arr.sort() var array = [arr[0 ]] for (var i = 1 ; i < arr.length; i++) { if (arr[i] !== arr[i - 1 ]) { array.push(arr[i]) } } return array } console .log(unique(arr))
利用 sort() 排序方法, 然后根据排序后的结果进行遍历及相邻元素比对.
sort() 方法用原地算法 对数组的元素进行排序,并返回数组。排序不一定是稳定的
由于本例数据类型很特殊, 导致排序出现无法估算的后果, sort 后 ‘字符串布尔值’ 和 ‘布尔值’ 顺序发生变化, 从而导致相邻元素类型不同
但如果数据结构简单或可以预估排序后是自己想要的结果, 可以根据排序后进行去重, 那么可以使用
对象无法去重, 而且需要自己预估排序后的结果是否符合可以去重的结果
五、利用对象的属性不能相同的特点进行去重 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function unique (arr ) { if (!Array .isArray(arr)) { console .log('type error!' ) return } var array = [] var obj = {} for (var i = 0 ; i < arr.length; i++) { if (!obj[arr[i]]) { array.push(arr[i]) obj[arr[i]] = 1 } else { obj[arr[i]]++ } } console .log(obj) return array } console .log(unique(arr))
NaN 和 数组/函数 等对象去重了, 但所有 Object 等去重过度了
所有 ‘字符串类基本类型’(如 ‘true’) 和 ‘基本类型’(如 true) 均去重了, 但也是去重过度
这都是由于对象的 [] 的 key 只能是 string, 你传递其他类型的 key, 最后都转成了 string
六、利用 includes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function unique (arr ) { if (!Array .isArray(arr)) { console .log('type error!' ) return } var array = [] for (var i = 0 ; i < arr.length; i++) { if (!array.includes(arr[i])) { array.push(arr[i]) } } return array } console .log(unique(arr))
所有对象(包括数组/函数)均没有去重, 其余均正确去重
七、利用 hasOwnProperty 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function unique (arr ) { var obj = {} var a = [] a = arr.filter(function (item, index, arr ) { return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true ) }) console .log(obj) return a } console .log(unique(arr))
NaN 和 数组/函数 等对象去重了, 但所有 Object 等去重过度了
利用 hasOwnProperty 判断是否存在对象属性, 和利用对象的 [] 一样, 无法判断 Object, 过度去重
八、利用 filter 1 2 3 4 5 6 7 8 9 function unique (arr ) { return arr.filter(function (item, index, arr ) { return arr.indexOf(item, 0 ) === index }) } console .log(unique(arr))
NaN 被去重了, 所有对象都没有去重
九、利用递归去重 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function unique (arr ) { var array = arr var len = array.length array.sort(function (a, b ) { return a - b }) function loop (index ) { if (index >= 1 ) { if (array[index] === array[index - 1 ]) { array.splice(index, 1 ) } loop(index - 1 ) } } loop(len - 1 ) return array } console .log(unique(arr))
和第四点类似, 都是先排序再去重, 但由于排序并不稳定, 导致这种方法并不可靠. 此例中排序由于使用 -
来排序, 会进行类型隐式转换
十、利用 Map 数据结构去重 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 function unique (arr ) { let map = new Map () let array = new Array () for (let i = 0 ; i < arr.length; i++) { if (map.has(arr[i])) { map.set(arr[i], true ) } else { map.set(arr[i], false ) array.push(arr[i]) } } console .log(map) return array } console .log(unique(arr))
创建一个空 Map 数据结构, 遍历需要去重的数组, 把数组的每一个元素作为 key 存到 Map 中. 由于 Map 中不会出现相同的 key 值, 所以最终得到的就是去重后的结果.
但由于对象保存的是引用地址值, 在咱们看来是相同的对象, 程序却根据引用值判断并不相同, 从而不会去重
所有对象(包括数组/函数)均没有去重, 其余均正确去重
十一、利用 reduce+includes 1 2 3 4 5 6 function unique (arr ) { return arr.reduce((prev, cur ) => (prev.includes(cur) ? prev : [...prev, cur]), []) } console .log(unique(arr))
原理和六一样, 只不过比六代码量少
十二、[…new Set(arr)] 1 2 console .log([...new Set (arr)])
代码就是这么少. 其实, 严格来说并不算是一种, 相对于第一种方法来说只是简化了代码
十三、根据类型去重 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function unique (arr ) { let newArr = [] let obj = {} arr.forEach(item => { if (typeof item !== 'object' ) { if (newArr.indexOf(item) === -1 ) { newArr.push(item) } } else { let str = JSON .stringify(item) if (!obj[str]) { newArr.push(item) obj[str] = 1 } } }) return newArr } console .log(unique(arr))
对象/数组去重成功, 但函数并没有去重, typeof 判断类型还是差一点
如果数组里都是 对象/数组, 那么可以使用下面方法
1 2 3 var strArr = arr.map(item => JSON .stringify(item))var uniqueStrArr = [...new Set (strArr)]var uniqueArr = uniqueStrArr.map(item => JSON .parse(item))