数组扁平化
Array.prototype.flat()(ES6)
按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回,对原数据没有影响。
depth
可选 指定要提取嵌套数组的结构深度,默认值为 1。
let arr1 = [1, 2, [3, 4]]
console.log(arr1.flat()) // [1, 2, 3, 4]
//默认深度为1
let arr2 = [1, 2, [3, 4, [5, 6]]]
console.log(arr2.flat()) // [1, 2, 3, 4, [5, 6]]
//指定展开 2 层深度的嵌套数组
let arr3 = [1, 2, [3, 4, [5, 6]]]
console.log(arr3.flat(2)) // [1, 2, 3, 4, 5, 6]
//使用 Infinity,可展开任意深度的嵌套数组
let arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]
console.log(arr4.flat(Infinity)) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
//扁平化跳过数组空项
let arr5 = [1, 2, , 3, 4]
console.log(arr5.flat()) // [1, 2, 3, 4]
//扁平化支持字符串和对象
let arr6 = [1, 2, ['hello world', [{ type: 'js' }, ['JavaScript']]]]
console.log(arr6.flat(Infinity)) // [1, 2, "hello world", {type: "js"}, "JavaScript"]
数组扁平化函数flat()实现方案
实现思路
首先遍历获取数组每个元素,然后判断该元素类型是否为数组,最后将数组类型的元素展开一层。同时递归遍历获取该数组的每个元素进行拉平处理。
遍历数组方案
const arr = [1, 1, 1, [2, 2, 2, [3, 3, 3, ['string', [{ type: 'object' }]]]]]
// for
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
// for...of
for (let value of arr) {
console.log(value)
}
// for...in
for (let i in arr) {
console.log(arr[i])
}
// forEach
arr.forEach(value => {
console.log(value)
})
// entries()
for (let [index, value] of arr.entries) {
console.log(value)
}
// keys()
for (let value of arr.values()) {
console.log(value)
}
// reduce()
arr.reduce((pre, cur) => {
console.log(cur)
}, [])
// map()
arr.map(value => console.log(value))
判断数组元素是否为数组
const arr = [1, 1, 1, [2, 2, 2, [3, 3, 3, ['string', [{ type: 'object' }]]]]]
// true 构造函数 Array 的 prototype 属性是否出现在实例对象(arr)的原型链上
console.log(arr instanceof Array)
// true 数组实例(arr)继承了Array.prototype.constructor 属性,它的值就是 Array
console.log(arr.constructor === Array)
// true toString() 返回一个字符串,表示指定的数组(arr)及其元素
console.log(Object.prototype.toString.call(arr) === '[object Array]')
// true Array.isArray() 用于确定传递的值(arr)是否是一个 Array
console.log(Array.isArray(arr))
注意:
instanceof
操作符是假定只有一种全局环境。在浏览器中,我们的脚本可能需要在多个窗口之间交互。多个窗口意味着多个全局环境,不同的全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数。这一种情况下使用instanceof
会不准确。constructor
可以被重写,不确保一定是数组。
数组元素展开一层方案
const arr = [1, 1, 1, [2, 2, 2, [3, 3, 3, ['string', [{ type: 'object' }]]]]]
// 扩展运算符 + concat
console.log([].concat(...arr)) // [1, 1, 1, 2, 2, 2, [3, 3, 3, ['string', [{ type: 'object' }]]]]
// concat + apply
console.log([].concat.apply([], arr)) // [1, 1, 1, 2, 2, 2, [3, 3, 3, ['string', [{ type: 'object' }]]]]
// toString + split
const arr2 = [1, 1, 1, [2, 2, 2, [3, 3, 3, [4, 4, 4, [5, 5, 5]]]]]
console.log(arr.toString().split(',').map(value => parseInt(value))) // [1, 1, 1, 2, 2, 2, 3, 3, 3, NaN, NaN]
console.log(arr2.toString().split(',').map(value => parseInt(value))) // [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5]
注意:
toString + split
方式只有数组中元素是数字时可行。不推荐使用
for / for in / for of + 递归实现 flat()
const arr = [1, 1, 1, , [2, 2, 2, , [3, 3, 3, ['string', [{ type: 'object' }]]]]]
//for
//使用 void 0 去除空位
const flat = (arr) => {
let res = []
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
res.push(...flat(arr[i]))
} else if (arr[i] !== void 0) {
res.push(arr[i])
}
}
return res
}
console.log(flat(arr)) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
//for in
function flat(arr) {
let res = []
for (let i in arr) {
if (Array.isArray(arr[i])) {
res.push(...flat(arr[i]))
} else {
res.push(arr[i])
}
}
return res
}
console.log(flat(arr)) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
//for of
const arr = [1, 1, 1, , [2, 2, 2, , [3, 3, 3, ['string', [{ type: 'object' }]]]]]
//循环不能去除数组空位,需要手动清除
function flat(arr) {
let res = []
for (let element of arr) {
if (Array.isArray(element)) {
res.push(...flat(element))
} else {
//去除空元素,添加非 undefined 元素(空元素在数字里会表现为 undefined )
element !== void 0 && res.push(element)
}
}
return res
}
console.log(flat(arr)) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
map / forEach + 递归实现 flat()
//map
const arr = [1, 1, 1, , [2, 2, 2, , [3, 3, 3, ['string', [{ type: 'object' }]]]]]
function flat(arr) {
let res = []
arr.map(item => {
if (Array.isArray(item)) {
res.push(...flat(item))
} else {
res.push(item)
}
})
return res
}
console.log(flat(arr)) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
//forEach
function flat(arr) {
let res = []
arr.forEach((element) => {
if (Array.isArray(element)) {
res.push(...flat(element))
} else {
res.push(element)
}
})
return res
}
console.log(flat(arr)) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
reduce + 递归实现 flat()
const arr = [1, 1, 1, , [2, 2, 2, , [3, 3, 3, ['string', [{ type: 'object' }]]]]]
function flat(arr) {
return arr.reduce((prev, cur) => {
return prev.concat(Array.isArray(cur) ? flat(cur) : cur)
}, [])
}
console.log(flat(arr)) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
用 Generator 实现 flat()
const arr = [1, 1, 1, , [2, 2, 2, , [3, 3, 3, ['string', [{ type: 'object' }]]]]]
function* flat(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flat(item)
} else if (item !== void 0) {
yield item
}
}
}
//调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
//也就是遍历器对象(Iterator Object)。所以我们要用一次扩展运算符得到结果。
var flattened = [...flat(arr)]
console.log(flattened) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
使用堆栈实现 flat()
const arr = [1, 1, 1, , [2, 2, 2, , [3, 3, 3, ['string', [{ type: 'object' }]]]]]
//使用 void 0 去除空位
function flat(arr) {
//将数组展开一层并保存
const stack = [...arr]
const res = []
//如果栈不为空
while (stack.length) {
//使用 pop 从 stack 中取出并移除值
const next = stack.pop()
if (Array.isArray(next)) {
//使用 push 送回内层数组中的元素,不会改变原始输入
stack.push(...next)
} else if (next !== void 0) {
res.push(next)
}
}
//反转恢复数组的顺序
return res.reverse()
}
console.log(flat(arr)) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
使用 reduce + 递归实现通过参数打平数组
const arr = [1, 1, 1, , [2, 2, 2, , [3, 3, 3, ['string', [{ type: 'object' }]]]]]
function flat(arr, deep = 1) {
//如果拉平深度大于0 则 reduce + 递归输出结果,否则浅拷贝当前数组
return deep > 0 ? arr.reduce((prev, cur) => prev.concat(Array.isArray(cur) ? flat(cur, deep - 1) : cur), []) : arr.slice()
}
console.log(flat(arr, 5)) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
使用正则打平数组
const arr = [1, 1, 1, , [2, 2, 2, , [3, 3, 3, ['string', [{ type: 'object' }]]]]]
//利用JSON可以深拷贝数组的原理,替换掉全局'[' + ']',在结果两边拼接上'[' + ']',然后过滤掉 undefined null
const res = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']').filter(d => d)
console.log(res) // [1, 1, 1, 2, 2, 2, 3, 3, 3, "string", {type: "object"}]
数组去重
使用 Set() 去重(ES6)
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
// 通过 new Set 转换成 Set 数据结构的数据
// 然后通过 Array.from 方法将 Set 数据结构的数据转换成正常数组
// 返回的时候就是已去重的数据
return Array.from(new Set(arr))
}
console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
//扩展运算符 + Set()
function unique(arr) {
return [...new Set(arr)]
}
console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
优点:代码最少
缺点:{}
不去重
使用双层循环+ splice() 去重(ES5常用)
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
var len = arr.length
for (var i = 0; i < len; i++) {
for (var j = i + 1; j < len; j++) {
// 检查是否有重复的元素
if (arr[i] === arr[j]) {
// 有就从数组里删去重复的元素
arr.splice(j, 1)
//splice方法是在原数组基础上修改,此时删去一个元素,则长度减1
len--
//保证 j 的值经过 j++ 后不变
j--
}
}
}
return arr
}
console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]
缺点:{}
和NaN
不去重
使用双层循环+ push() 去重(ES5常用)
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
var res = []
var len = arr.length
//遍历数组中所有元素
for (var i = 0; i < len; i++) {
for (var j = i + 1; j < len; j++) {
// 检查是否有重复的元素,如果有终止当前循环同时进入顶层循环的下一轮判断
if (arr[i] === arr[j]) {
j = ++i
}
}
//获取没有重复的值放入新的数组
res.push(arr[i])
}
return res
}
console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]
缺点:{}
和NaN
不去重
使用 includes() + 遍历原数组去重
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var res = []
for (var i = 0; i < arr.length; i++) {
//如果新数组res不存在当前值,则将当前值推入res中
if (!res.includes(arr[i])) {
res.push(arr[i])
}
}
return res
}
console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
缺点:{}
不去重
使用 indexOf() + 遍历原数组去重
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var res = []
var len = arr.length
for (var i = 0; i < len; i++) {
//如果新数组res不存在当前数组下标的元素,则将元素推入res中
if (res.indexOf(arr[i]) === -1) {
res.push(arr[i])
}
}
return res
}
console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]
缺点:{}
和NaN
不去重
使用 sort() + 相邻元素比较去重
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
//先排序
arr = arr.sort()
var len = arr.length
//取出第一位元素
var res = [].concat(arr[0])
//从第二位元素开始遍历
for (var i = 1; i < len; i++) {
//如果原数组相邻的两个元素值不相等
if (arr[i] !== arr[i - 1]) {
//将元素推入新数组 res
res.push(arr[i])
}
}
return res
}
console.log(unique(arr)) // [0, 1, 15, NaN, NaN, "NaN", {…}, {…}, "a", false, null, "true", true, undefined]
缺点:{}
和NaN
不去重
使用 filter() + includes() 去重
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
var res = []
return arr.filter(function (item, index, arr) {
//使用过滤器,如果res包含值,返回空字符串,否则将元素值推入新数组res
return res.includes(item) ? '' : res.push(item)
})
}
console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
缺点:{}
不去重
使用 filter() + hasOwnProperty() 去重(ES5,支持对象去重)
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
var res = {}
return arr.filter(function (item, index, arr) {
return res.hasOwnProperty(typeof item + item) ? false : (res[typeof item + item] = true)
})
}
console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]
优点:支持所有数据类型去重
这种方法是利用空对象,把数组的值保存成 Object 的 key 值,比如
Object[value] = true
,在判断另一个值时,如果 Object[value] 存在就说明该值是重复的。没有直接使用
obj[item]
是因为 1 和 "1" 是不同的,直接使用前面的方法会判断为同一个值。因为对象的键值只能是字符串,所以我们可以使用
typeof item + item
拼成字符串作为key 来避免这个问题。
使用递归去重
var arr = [1, 1, '1', '1', 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
var res = arr
var len = res.length
res.sort(function (a, b) {
return a - b //先排序,方便去重
})
function loop(index) {
if (index >= 1) {
if (res[index] === res[index - 1]) {
res.splice(index, 1)
}
loop(index - 1)
}
}
loop(len - 1)// 递归loop,然后数组去重
return res
}
console.log(unique(arr)) // [1, "1", "true", false, null, true, 15, NaN, NaN, "NaN", 0, "a", {…}, {…}, undefined]
缺点:{}
和NaN
不去重
使用 Map 数据结构去重
var arr = [1, 1, '1', '1', 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
let map = new Map()
let res = []
for (let i = 0; i < arr.length; i++) {
if (map.has(arr[i])) {
map.set(arr[i], true)
} else {
map.set(arr[i], false)
res.push(arr[i])
}
}
return res
}
console.log(unique(arr)) // [1, "1", "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
缺点:{}
不去重
使用 reduce() + includes() 去重
var arr = [1, 1, '1', '1', 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 'NaN', 0, 0, 'a', 'a', {}, {}]
function unique(arr) {
//reduce 累加器 如果包含元素就返回累加器,否则返回累加器和 cur 值的数组,thisArgs 是空数组,也就是累加器默认也是空数组
return arr.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], [])
}
console.log(unique(arr)) // [1, "1", "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
缺点:{}
不去重