前言前言前言做前端开发几年,在项目中用到数组去重的机会倒不是很多,但是在面试的时候却经常被问到,个人理解,这道题真正考的是对JavaScript的基础的掌握,因为有很多种方式可以做到。这次就根据这道题,将相关的知识理解透彻。一、ES6中的new Set方式一、ES6中的new Set方式一、ES6中的new Set方式先看看MDN上对Set的描述:
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。原始值关键字:任何类型都可以存储、存储进来的值都是唯一的,这样就可以做到先把数组中重复的数据去掉。
const list1 = [1, 2, 3, 7, 11, 56, 3, 2, 4, 5]
const list2 = new Set(list1)
console.log(list2)

const list1 = [1, 2, 3, 7, 11, 56, 3, 2, 4, 5]
const list2 = new Set(list1)
console.log(list2)
看一下返回的结果:由结果可知,返回了一个可迭代的Set对象,此时还需要把Set对象转为数组。此时可以用到
Array.from()。
**Array.from()** 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例.
**Array.from()** 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例.刚好new Set()返回的就是一个可迭代的对象。
const list3 = Array.from(new Set([null, null, undefined, 12, undefined, 7, 2, 1, 22, 2, function a(){}, function a(){}, {}, {}]))

const list3 = Array.from(new Set([null, null, undefined, 12, undefined, 7, 2, 1, 22, 2, function a(){}, function a(){}, {}, {}]))
结果是返回了一个数组:这种方式不考虑兼容性,且去不掉重复的function和{}关于Array.from()的扩展关于Array.from()的扩展
Array.from(arrayLike[, mapFn[, thisArg]])
Array.from(arrayLike[, mapFn[, thisArg]])arrayLike: 伪数组和可迭代对象mapFn:每个元素会执行这个回调方法thisArg: 执行回调函数时this的指向。
const list4 = Array.from('name', arg => arg + 12)
console.log(list4) // [ 'n12', 'a12', 'm12', 'e12' ]

const list4 = Array.from('name', arg => arg + 12)
console.log(list4) // [ 'n12', 'a12', 'm12', 'e12' ]
伪数组对象(拥有一个 length 属性和若干索引属性的任意对象, 如string)可迭代对象(可以获取对象中的元素,如 Map和 Set 等)二、set的另一种写法二、set的另一种写法二、set的另一种写法
const list20 = [5, 3, 5, 5, 6, 37, 22, 22]
console.log([...new Set(list20)])

const list20 = [5, 3, 5, 5, 6, 37, 22, 22]
console.log([...new Set(list20)])
其实跟第一种差不多三、嵌套的for循环
三、嵌套的for循环
三、嵌套的for循环

const list5 = [null, null, undefined, undefined, {}, {}, function q() {}, function q() {}, 34, 2, 1, 2]
for(let i = 0; i < list3.length; i++) {

for (let j = i + 1; j < list3.length; j++) {

if (list3[i] === list3[j]) {

list3.splice(j, 1) // 将重复的数据删掉一个

j-- // 因为删除掉了一个元素,就从这个元素的索引重新开始

}

}
}

const list5 = [null, null, undefined, undefined, {}, {}, function q() {}, function q() {}, 34, 2, 1, 2]
for(let i = 0; i < list3.length; i++) {

for (let j = i + 1; j < list3.length; j++) {

if (list3[i] === list3[j]) {

list3.splice(j, 1) // 将重复的数据删掉一个

j-- // 因为删除掉了一个元素,就从这个元素的索引重新开始

}

}
}
这种方式用了splice(index, num)的方法,返回的结果也没有把function和对象给去掉。我们能想到的最简单的方式就是这种去重方式。四、indexOf方式四、indexOf方式四、indexOf方式
const list6 = [null, null, undefined, undefined, NaN, NaN, false, 'false', function a(){}, function a() {}, {}, {}]
const list7 = []
for(let k = 0; k
if (list7.indexOf(list6[k]) === -1) {

list7.push(list6[k])

}
}
console.log(list7, 'list7')
// [null, undefined,NaN, NaN,false,'false',[Function: a], [Function: a],{},{}] list7

const list6 = [null, null, undefined, undefined, NaN, NaN, false, 'false', function a(){}, function a() {}, {}, {}]
const list7 = []
for(let k = 0; k
if (list7.indexOf(list6[k]) === -1) {

list7.push(list6[k])

}
}
console.log(list7, 'list7')
// [null, undefined,NaN, NaN,false,'false',[Function: a], [Function: a],{},{}] list7
关于indexOfArray构造函数和String构造函数的原型上都有这个方法MDN上对他们的解释分别是:
**indexOf()**方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
**indexOf()**方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
arr.indexOf(searchElement[, fromIndex])

arr.indexOf(searchElement[, fromIndex])

**indexOf()** 方法返回调用它的 String 对象中第一次出现的指定值的索引,从 fromIndex 处进行搜索。如果未找到该值,则返回 -1。
**indexOf()** 方法返回调用它的 String 对象中第一次出现的指定值的索引,从 fromIndex 处进行搜索。如果未找到该值,则返回 -1。
str.indexOf(searchValue [, fromIndex])

str.indexOf(searchValue [, fromIndex])
这么一看 就知道他俩用法一样。五、sort五、sort五、sort先看看关于sort的解释:
**sort()** 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的
由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。
**sort()** 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的
由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。原地算法所以这种方式,在数组长的时候可能保证不了性能。用它去重的思路是什么呢?先用sort排序,然后前一个跟后一个比较,这样相同的值永远都是邻关系。
const list8 = [null, null, undefined, undefined, NaN, NaN, false, 'false', function a(){}, function a() {}, {}, {}]
const list9 = list8.sort()
const list10 = [list9[0]]
for(let i = 1; i
if (list9[i] !== list9[i-1]) {

list10.push(list9[i])

}
}

const list8 = [null, null, undefined, undefined, NaN, NaN, false, 'false', function a(){}, function a() {}, {}, {}]
const list9 = list8.sort()
const list10 = [list9[0]]
for(let i = 1; i
if (list9[i] !== list9[i-1]) {

list10.push(list9[i])

}
}
这一看就很容易理解。六、includes六、includes六、includes
const list11 = [null, null, undefined, undefined, NaN, NaN, false, 'false', function a(){}, function a() {}, {}, {}]
const list12 = []
for (let i = 0; i
if (!list12.includes(list11[i])) {

list12.push(list11[i])

}
}

const list11 = [null, null, undefined, undefined, NaN, NaN, false, 'false', function a(){}, function a() {}, {}, {}]
const list12 = []
for (let i = 0; i
if (!list12.includes(list11[i])) {

list12.push(list11[i])

}
}
此结果没有把function和{}去重,其他的都去重了。includes也是Array构造函数和String构造函数上都有的方法。七、filter和indexOf七、filter和indexOf七、filter和indexOf
// 数组去重方式6-filter和indexOf
const list13 = [1, 2, 3, 3, 5]
const list14 = list13.filter((item, index, arr) => {

// 也就是在遍历这个数组list13的时候,每次拿当前的元素跟此数组中这个元素第一次出现的位置相比,如果位置是一致的,就返回当前元素

return list13.indexOf(item, 0) === index
})

// 数组去重方式6-filter和indexOf
const list13 = [1, 2, 3, 3, 5]
const list14 = list13.filter((item, index, arr) => {

// 也就是在遍历这个数组list13的时候,每次拿当前的元素跟此数组中这个元素第一次出现的位置相比,如果位置是一致的,就返回当前元素

return list13.indexOf(item, 0) === index
})
八、递归八、递归八、递归
const list15 = [1, 2, 3, 4, 5, 7, 6, 4, 3]
const list16 = list15
const len = list16.length
list16.sort(function(a, b) { return a - b }) // 先排序
function loop(index) {

if (index >= 1) {

if (list16[index] === list16[index - 1]) {

list16.splice(index, 1)

}

loop(index - 1)

}
}
loop(len - 1)

const list15 = [1, 2, 3, 4, 5, 7, 6, 4, 3]
const list16 = list15
const len = list16.length
list16.sort(function(a, b) { return a - b }) // 先排序
function loop(index) {

if (index >= 1) {

if (list16[index] === list16[index - 1]) {

list16.splice(index, 1)

}

loop(index - 1)

}
}
loop(len - 1)
关于sort排序关于sort排序在排序的时候 我们可能忽略了一个问题,可能得到的并不是我们想要的结果,比如下面的例子:
const list17 = [22, 1, 2, 15, 3, 4, 3, 1, 11]
console.log(list17.sort()) // [1, 1, 11, 15, 2, 22, 3, 3, 4]

const list17 = [22, 1, 2, 15, 3, 4, 3, 1, 11]
console.log(list17.sort()) // [1, 1, 11, 15, 2, 22, 3, 3, 4]
看看得到的结果,根本就不是按照从小到大排的。这是因为:默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的。
所以 要另找一种排序的方法,就是给sort传入一个函数。
arr.sort([compareFunction])
arr.sort([compareFunction])参数:

compareFunction: 可传可不传,传的话用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。

compareFunction的参数1-firstEl: 用于比较的第一个元素

compareFunction的参数2-secondEl: 用于比较的第二个元素

如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;

如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);

如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。

compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
compareFunction: 可传可不传,传的话用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。compareFunction的参数1-firstEl: 用于比较的第一个元素compareFunction的参数2-secondEl: 用于比较的第二个元素如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。返回值:返回值:返回的是已经原地排序的数组。九、用Map数据结构排序九、用Map数据结构排序九、用Map数据结构排序
const list 18 = [2, 3, 33, 2, 5, 1, 3]
const map1 = new Map()
for(let i = 0; i
if (map1.get(list18[i])) {

map1.set(list18[i], true)

} else {

map1.set(list18[i], false)

list19.push(list18[i])

}
}

const list 18 = [2, 3, 33, 2, 5, 1, 3]
const map1 = new Map()
for(let i = 0; i
if (map1.get(list18[i])) {

map1.set(list18[i], true)

} else {

map1.set(list18[i], false)

list19.push(list18[i])

}
}
这个比较好理解, 就不多说了。总结总结总结