【深度剖析】JavaScript数组去重(8种方法)

前言

在工作时,处理后端传来的一棵树,偶然发现最后生成的数组存在重复的值。这种时候,当然是要和后端进行交流的。虽然在前端也可以处理,但是如果这棵树也被使用到其他地方,那就需要多次处理,显然前端对数组进行去重是不合理的。但去重的方法也需要掌握,于是有了这篇文章。

数组去重的8种方法

先来个数组,把所有类型都包含进来:

const arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}]

一、利用new Set():

这种方法是ES6种最常用的数组去重方法。
function deduplication(arr) {
    return Array.from(new Set(arr))
}

console.log(deduplication(arr))

二、利用展开运算符“...”:

这种方法显而易见,就是简化了new Set()。其实应该和new Set()算作同一种方法。
console.log([...new Set(arr)])

三、利用for循环+splice()去重:

太基础了,但基础的东西最重要。关键在于j = i + 1,这一步操作将内外层循环紧密联系起来,就是说如果外层的i为0,则内层的j为从1开始遍历,也就是说,每次循环都会比较i = 0和j = 从1开始的每一个元素,判断这两个元素是否全等,如果全等则将索引为j的元素从数组移出,也就是说!!!如果第一趟的数组length为12,则第二趟它的length为12或更小。
function deduplication(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[i] === arr[j]) {
                arr.splice(j, 1)
            }
        }
    }
    return arr
}

四、利用indexOf():

也很基础,也很重要,重点在于对空数组使用indexOf方法。我们都知道如果indexOf的元素如果在数组中不存在,则会返回-1,于是利用这个反思路,如果是-1就将这个值推到一个新的数组中。
function deduplication(arr) {
    if (!Array.isArray(arr)) {
        throw Error("TypeError: 参数必须为数组类型")
    }
    let newArr = []
    for (let i = 0; i < arr.length; i++) {
        if (newArr.indexOf(arr[i]) === -1) {
            newArr.push(arr[i])
        }
    }
    return newArr
}

五、利用sort():

先对数组进行排序,然后把排序后的第一个元素放到新数组中,这么做是为了防止i - 1 < 0,然后for循环时i的初始值就是1。然后后一个和前一个比较,如果不全等则推到新数组中。
function deduplication(arr) {
    if (!Array.isArray(arr)) {
        throw Error("TypeError: 参数必须为数组类型")
    }
    arr.sort()
    let newArr = [arr[0]]
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i - 1]) {
            newArr.push(arr[i])
        }
    }
    return newArr
}

六、利用includes():

原理很简单,但需要充分理解includes这个方法。它返回的是一个boolean类型值,就是说如果这个数组包含这个值则为true,否则为false。按照这个逻辑,如果一个新数组中包含了这个值则不会往这个数组里push这个元素。反过来说,如果这个值不在新数组中,那么就把这个值push到这个新数组。

function deduplication(arr) {
    if (!Array.isArray(arr)) {
        throw Error("TypeError: 参数必须为数组类型")
    }
    let newArr = []
    for (let i = 0; i < arr.length; i++) {
        if (!newArr.includes(arr[i])) {
            newArr.push(arr[i])
        }
    }
    return newArr
}

七、利用filter():

对数组进行过滤,return后面的内容是过滤的条件,会返回一个新的数组。这里的条件利用了indexOf(item, 0),这个用法很少用到但并不难理解,item不用多说表示的是要查找的元素,而0则表示start,也就是起始位置,从索引为0开始查找。那么进行过滤的条件就表示,如果这个元素在原数组中首次出现的位置等于当前项的索引则push到新数组中。
function deduplication(arr) {
    return arr.filter((item, index, arr) => {
        //console.log(item, index, arr) //当前项、当前项索引、数组本身
        //console.log(arr.indexOf(item, 0)) //当前项在原数组首次出现的位置
        return arr.indexOf(item, 0) === index
    })
}

八、利用reduce():

首先需要对reduce进行一个深入了解。图中写错了,不是和,是计算结果。例如return prev + next则返回的是和,同样return prev * next则返回的是乘积。

image.png

还是举个例子吧:

image.png

然后开始利用reduce进行去重,理解了reduce的用法之后就简单很多了。这段代码的关键在于传入初始值“[]”,就是说首先需要让prev变成一个数组,然后在遍历时判断这个数组中是否包含了当前值,如果已经包含则prev不变,如果没有则将当前值拼接到prev中。
function deduplication(arr) {
    return arr.reduce((prev, next) => {
        // console.log(prev, next) //prev初始为[]
        // console.log(Array.isArray(prev)) //true
        return prev.includes(next) ? prev : prev.concat(next)
    }, []) //“[]”为传递给函数的初始值
}
tips:数组的concat可以拼接任意对象

image.png

总结

数组去重这种操作在前端几乎不会用到,除非某些特别情况下。但研究这些方法能够让我们对其用法以及原理有更加深刻的掌握和理解。

Keep foolish, keep hungry.