JavaScript的数组去重、扁平化方法详解 --每天进步一点点

440 阅读4分钟

去重的方法

去重是开发中经常会碰到的一个热点问题,不过目前项目中碰到的情况都是后台接口使用SQL去重,简单高效,基本不会让前端处理去重。

当然,这并不是说前端去重就没有必要了,依然需要会熟练使用。下面主要介绍几种常见的数组去重的方法。

1. hasOwnProperty()+ filter + 对象属性不能重复

所有的都去重了

  1. hasOwnProperty() 方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。

function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  let obj = {}
  arr.filter(item => obj.hasOwnProperty(typeOf item + item) ? false : obj[typeOf item + item] = true)
}

2. ES6 set + 解构赋值(常用)

对象没有去重

  1. 最简单,最常用的方法,没有之一。

  2. Set的一个最大的特点就是数据不重复。

  3. Set函数可以接受一个数组(或类数组对象)作为参数来初始化。


function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  return [...new Set(arr)]
}

3. ES6 set + Array.from

对象没有去重

Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。


function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  return Array.from(new Set(arr))
}

4. includes

对象没有去重

  1. includes() 方法判断数组是否包含一个指定的值,包含则返回 true,否则返回false。

  2. [NaN].includes(NaN) 为 true


function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  let res = []
  arr.forEach(item => {
    if (!res.includes(item)) {
      res.push(item)
    }
  })
  return res
}

5. reduce + includes

对象没有去重

  1. reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

  2. reduce() 包含两个参数:回调函数,传给total的初始值

  3. [NaN].includes(NaN) 为 true


function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  return arr.reduce((total, cur) => total.includes(cur) ? pre : [...pre, cur], [])
}

6. 利用for嵌套for,然后splice去重(ES5中最常用)

NaN和对象没有去重


function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = arr.length; j > i; j--) {
      if (arr[i] === arr[j]) arr.splice(j, 1)
    }
  }
  return arr
}

j 从最后向前找比较好,因为:数组 splice 一个元素后,后面的元素索引都向前一位。

7. indexOf

NaN和对象没有去重


function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  let res = []
  arr.forEach(item => {
    if (res,indexOf(item) === -1) {
      res.push(item)
    }
  })
  return res
}

8. indexOf + filter

NaN和对象没有去重

  1. filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  return arr.filter((item, index, arr) => arr.indexOf(item) === index)
}

unique([NaN, NaN]) 返回 [],因为 arr.indexOf(NaN) 都为 -1

9. sort()

NaN和对象没有去重

  1. 利用 sort() 排序方法,然后根据排序后的结果进行遍历及相邻元素比对。

  2. sort() 方法用原地算法对数组的元素进行排序。详细请移步:Array.prototype.sort()


function unique(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  arr = arr.sort()
  let res = arr[0] && [arr[0]]
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] !== arr[i-1]) {
      res.push(arr[i])
    }
  }
  return res
}

总结

按照去重的结果来看

  1. 所有都去重:

    • 1.hasOwnProperty()+ filter + 对象属性不能重复
  2. 对象没有去重

    • 2.ES6 set + 解构赋值去重(常用)
    • 3.ES6 set 与 Array.from 去重
    • 4.includes
    • 5.reduce + includes
  3. NaN和对象没有去重

    • 6.利用for嵌套for,然后splice去重(ES5中最常用)
    • 7.indexOf
    • 8.indexOf + filter
    • 9.sort()

按照去重的方式来看

除了 6.利用for嵌套for,然后splice去重(ES5中最常用) 在原来的数组上修改,其他的都是创建一个新数组,然后通过各种方法将不重复的元素追加到新的数组中,返回新数组。

扁平化的方法

数组扁平化即将一个嵌套多层的数组array(嵌套可以是任意层数)转换为只有一层的数组,如将数组[1,[2,[3,[4,5]]]]转换为[1,2,3,4,5]。

最直接的数组扁平化方案是使用 Array.prototype.flat() 方法(兼容性差),其次是通过遍历数组元素递归实现每一层的数组拉平。

1. ES6 flat

  1. 语法:res = arr.flat([depth])

  2. 说明:

    • depth为指定要提取嵌套数组的结构深度,默认值为1。
    • 参数 depth <= 0 时返回原数组;
    • 参数depth为 Infinity 关键字时,无论多少层嵌套,都会转为一维数组,
    • flat() 方法会移除数组中的空项,即原数组有空位,会跳过这个空位。

2. reduce

  1. 遍历数组每一项,若值为数组则递归遍历,否则concat。

function flatten(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  return arr.reduce((total, cur) => total.concat(Array.isArray(cur) ? flatten(cur) : cur), [])
}

3. toString (join) & split

  1. 如果数组的项全为数字,可以使用 join(),toString() 把数组转换成字符串,这个过程会把[] 都去掉,然后再调用 split() 方法转换成数组。

    数组的每一项都执行了 toString(),所以数字也变成了字符串。


function flatten(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

  return arr.toString().split(',').map(item => +item)

  ---------------------------------------------------

  return arr.join(',').split(',').map(item => +item)
}

字符串就不需要 map() 了。

4. 递归(也可以用栈)

reduce 一样,用 reduce 比这个简洁。


function flatten(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError('arr must be an Array')
  }

let res = []
arr.forEach(item => res.concat(Array.isArray(item) ? flatten(item) : item))
return res
}

总结

除直接使用 flat() ,其他的方法都是:遍历数组元素递归实现每一层的数组拉平。

tree扁平化

1. 递归实现

遍历tree,每一项加进结果集,如果有children且长度不为0,则递归遍历。

这里需要用解构赋值将每个节点的 children属性去除。

function treeToArray(tree) {
  let res = []
  for (const item of tree) {
    const { children, ...i } = item
    if (children && children.length) {
      res = res.concat(treeToArray(children))
    }
    res.push(i)
  }
  return res
}

2. reduce

思路同递归实现,简洁一点

function treeToArray(tree) {
  return tree.reduce((res, item) => {
    const { children, ...i } = item
    return res.concat(i, children && children.length ? treeToArray(children) : [])
  }, [])
}

参考文献

  1. JavaScript数组去重(12种方法,史上最全)
  2. 7种方法实现数组去重
  3. JS数组扁平化的一些方法(7-8种)