1.创建数组的四种方式
1.1 字面量创建数组
const arr = ['Java', 'JavaScript', 'Golang']1.2 通过 Array 实例化数组
const arr = Array('Java', 'JavaScript', 'Golang')1.3 创建数组并赋值
const arr = new Array()
arr[0] = 'Java'
arr[1] = 'JavaScript'
arr[2] = 'Golang'1.4 通过 Array.of()创建数组
const arr = Array.of('Java', 'JavaScript', 'Golang')1.5 通过 Array.from()指定 length 属性生成数字序列
/**
* Array.from()可以指定length属性生成数字序列,生成后的数组元素都使用undefined初始化
*/
const arr = Array.from({ length: 3 })
console.log(arr) // [undefined,undefined,undefined]
// 通过Array.from()生成0到10(不含10)之间的数组
Array.from({ length: 10 }, (_, index) => index)2.数组 Api
/**
* [
at: ƒ at()
concat: ƒ concat()
constructor: ƒ Array()
copyWithin: ƒ copyWithin()
entries: ƒ entries()
every: ƒ every()
fill: ƒ fill()
filter: ƒ filter()
find: ƒ find()
findIndex: ƒ findIndex()
flat: ƒ flat()
flatMap: ƒ flatMap()
forEach: ƒ forEach()
includes: ƒ includes()
indexOf: ƒ indexOf()
join: ƒ join()
keys: ƒ keys()
lastIndexOf: ƒ lastIndexOf()
length: 0
map: ƒ map()
pop: ƒ pop()
push: ƒ push()
reduce: ƒ reduce()
reduceRight: ƒ reduceRight()
reverse: ƒ reverse()
shift: ƒ shift()
slice: ƒ slice()
some: ƒ some()
sort: ƒ sort()
splice: ƒ splice()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
unshift: ƒ unshift()
values: ƒ values()
Symbol(Symbol.iterator): ƒ values()
Symbol(Symbol.unscopables): {copyWithin: true, entries: true, fill: true, find: true, findIndex: true, …}
[[Prototype]]: Object
]
*/
console.log(Array.prototype)2.1 Array.prototype.at()
at(index):该方法接受一个整数值并返回该索引处的项目,允许正整数和负整数,负整数从数组中的最后一项开始计数,index 默认值为 0。
const arr = ['Java', 'JavaScript', 'Golang', 'TypeScript', 'Shell']
console.log(arr.at()) // Java
console.log(arr.at(0)) // Java
console.log(arr.at(2)) // Golang
console.log(arr.at(-2)) // TypeScript2.2 Array.prototype.concat()
concat(arr...):连接一个或多个数组,返回连接后的数组,不会改变原数组。
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr3 = [7, 8, 9]
console.log(arr1.concat(arr2, arr3), arr1) // [1,2,3,4,5,6,7,8,9] [1,2,3]
console.log(['Java', 'JavaScript'].concat(['Golang'], ['TypeScript'])) // ['Java','JavaScript','Golang','TypeScript']2.3 Array.prototype.copyWithin()
copyWithin(target,start,end):将数组的一部分浅复制到同一数组中的另一个位置,并在不修改其长度的情况下返回它,此方法会改变原数组。
- target:将序列复制到的从零开始的索引。如果为负数,target 将从最后开始计数。如果 target 等于或大于 arr.length,则不会复制任何内容。如果 target 位于之后 start,复制的序列将被修剪以适应 arr.length。
- start(可选):从零开始复制元素的索引。如果为负数,start 将从最后开始计数。如果 start 省略,copyWithin 将从下标 0 复制。
- end(可选):从零开始的索引,从中结束复制元素。copyWithin 复制最大长度为数组.length,如果为负数,end 将从最后开始计数。如果 end 省略,copyWithin 将复制到最后一个索引(默认为 arr.length)。
const arr = ['a', 'b', 'c', 'd', 'e']
// 复制arr从下标2到下标4的元素到数组的下标0
console.log(arr.copyWithin(0, 2, 4), arr) // ['c', 'd', 'c', 'd', 'e'] ['c', 'd', 'c', 'd', 'e']
console.log(['a', 'b', 'c', 'd', 'e'].copyWithin(2, 2, 4)) // ['a', 'c', 'd', 'd', 'e']2.4 Array.prototype.entries()
entries():返回一个新的 Array Iterator 对象,该对象包含数组中每个索引的键/值对。
const arr = ['Java', 'JavaScript', 'Golang']
const iterator = arr.entries()
console.log(iterator.next().value) // [0,'Java']
console.log(iterator.next().value) // [1,'JavaScript']
/*
* 通过 for of 遍历Iterator(可迭代)对象,for of是ES6提供的新特性,
* 用于替代for in 和 forEach(),for of 允许遍历Array、String、Maps、
* Sets等迭代数据结构。
*/
for (const [key, item] of arr.entries()) {
/*
* 0 'Java'
* 1 'JavaScript'
* 2 2 'Golang'
*/
console.log(key, item)
}2.5 Array.prototype.every()
every(callback(element,index,arr),thisArg):测试数组内所有元素是否都能通过 callback 函数的测试,若全部通过则返回 true,否则返回 false。
- callback:用来测试每个元素的函数,它接收 element(当前元素)、index(当前元素的下标)、array(调用 every 的当前数组)。
- thisArg:执行 callback 时使用的 this 值。
const arr1 = [12, 5, 8, 130, 44]
const arr2 = [12, 54, 19, 23, 322]
const arr3 = [1, -1, 2, 123, 75]
console.log(arr1.every(item => item > 10)) // false
console.log(arr2.every(item => item > 10)) // true
console.log(arr3.every(item => Boolean(item))) // false2.6 Array.prototype.fill()
fill(value,start,end):用一个固定值填充一个数组从起始索引到终止索引的全部元素(不含终止索引)。value 为填充数组元素的值,start(可选,默认为 0)填充的起始索引,end(可选,默认为 arr.length)填充的终止索引,此方法会改变原数组。
console.log([1, 2, 3].fill(4)) // [4,4,4]
// 下标1到arr.length使用4填充
console.log([1, 2, 3].fill(1, 4)) // [1,4,4]
console.log([1, 2, 3].fill(4, -3, -2)) // [4,2,3]
console.log([1, 2, 3].fill(4, NaN, NaN)) // [1,2,3]2.7 Array.prototype.filter()
filter(callback(element,index,array),thisArg):创建一个新数组,其数组包含通过 callback 函数实现的测试的所有元素。
- callback:用来测试每个元素的函数,返回 true 表示该元素通过测试,保留该元素,返回 false 时则不保留,它接收 element(当前元素)、index(当前元素的下标)、array(调用 every 的当前数组)。
- thisArg:执行 callback 时使用的 this 值。
const arr1 = [12, 4, 90, 11, 123]
console.log(arr.filter(item => item > 10)) // [12,90,11,123]
const fruits = ['apple', 'banana', 'grapes', 'mango', 'orange']
const filterItems = query => {
// toLowerCase()用于将字符串转为英文小写
return fruits.filter(item => item.toLowerCase().indexOf(query.toLowerCase()) > -1)
}
console.log(filterItems('ap')) // ['apple','grapes'];
console.log(filterItems('an')) // ['banana','mango','orange']
// 过滤数组中隐式类型转换为false的元素
constle.log([0, 1, false, 2, '', 3].filter(Boolean)) // [1,2,3]2.8 Array.prototype.find()
find(callback(element,index,array),thisArg):返回数组中满足 callback 函数条件的第一个元素的值,否则返回 undefined。
- callback:在数组每一项执行的函数,它接收 element(当前元素)、index(当前元素的下标)、array(调用 every 的当前数组)。
- thisArg:执行 callback 时使用的 this 值。
const arr = [
{ name: '张三', age: 19 },
{ name: '李四', age: 4 },
{ name: '王五', age: 60 },
{ name: '李老六', age: 35 },
]
console.log(arr.find(item => item.age > 18)) // {name: '张三', age: 19}
console.log(arr.find(item => item.age > 66)) // undefined2.9 Array.prototype.findIndex()
find(callback(element,index,array),thisArg):返回数组中满足 callback 函数条件的第一个元素的下标,否则返回 -1。
- callback:在数组每一项执行的函数,它接收 element(当前元素)、index(当前元素的下标)、array(调用 every 的当前数组)。
- thisArg:执行 callback 时使用的 this 值。
const arr = [
{ name: '张三', age: 19 },
{ name: '李四', age: 4 },
{ name: '王五', age: 60 },
{ name: '李老六', age: 35 },
]
console.log(arr.findIndex(item => item.age > 18)) // 0
console.log(arr.findIndex(item => item.age > 66)) // -12.10 Array.prototype.flat
flat(depth):方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。 简单来说将一个多维数组根据指定深度(depth)将数组拍平,depth 为拍平的深度,默认值为 1,当 depth 为 Infinity 时, 表示可展开任意深度的嵌套数组,无论数组嵌套多深,最后都会被拍平为一维数组。注意:flat()会移除数组中的空元素。
const arr1 = [1, , 2, [3, 4]]
// flat() 会移除数组中的空项
console.log(arr1.flat()) // [1,2,3,4]
const arr2 = [1, 2, [3, 4, [5, 6]]]
console.log(arr2.flat()) // [1,2,3,4,[5,6]]
const arr3 = [1, 2, [3, 4, [5, 6]]]
console.log(arr2.flat(2)) // [1,2,3,4,5,6]
const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]
// 使用 Infinity 展开任意深度的嵌套数组,将多维数组拍平为一维数组
console.log(arr4.flat(Infinity)) // [1,2,3,4,5,6,7,8,9,10]除了 flat()可以实现数组的扁平化之外,也可以通过其他方式来替代 flat():
// 方式1:通过reduce + concat + isArray + 递归实现flat()
const arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]]
Array.prototype.flatDeep = function (depth = 1) {
/*
* 如果depth不大于0那么会通过slice()直接返回源数组的一份浅拷贝,如果大于0将使用reduce()对数组做规约,
* 通过concat()将元素连接,如果元素是数组时那么会递归调用flatDeep(),由于使用reduce()进行规约,
* 所以depth需要* * -1,如果元素不是数组那么直接连接。
*/
return depth > 0
? this.reduce((acc, val) => acc.concat(Array.isArray(val) ? val.flatDeep(depth - 1) : val), [])
: this.slice()
}
console.log(arr1.flatDeep()) // [1, 2, 3, 1, 2, 3, 4, [2,3,4]]
console.log(arr1.flatDeep(Infinity)) // [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]3.数组添加元素的 6 种方式
3.1 push()尾部添加元素
const arr = [1]
arr.push(2, 3)
console.log(arr) // [1,2,3]3.2 unshift()头部添加元素
const arr = [3]
arr.unshift(1, 2)
console.log(arr) // [1,2,3]3.3 concat()连接数组
const arr = [1]
const newArr = arr.concat([2, 3])
console.log(newArr) // [1,2,3]3.4 splice()指定位置添加/删除元素
const arr = [1]
arr.splice(arr.length, 0, 2, 3)
console.log(arr) // [1,2,3]3.5 splice()指定位置添加/删除元素
const arr = [1]
const newArr = [...arr, 2, 3]
console.log(newArr) // [1,2,3]3.6 Array.length
const arr = [1]
arr[arr.length] = 2
arr[arr.length] = 3
console.log(arr) // [1,2,3]4.数组删除元素的 4 种方式
4.1 伪删除将元素设置为 null
const arr = [1, 2, 3, 4, 5]
arr[2] = null
// 虽然第三个元素被赋值为null,但数组的长度仍不变
console.log(arr) // [1,2,null,4,5]4.2 splice()删除指定下标元素
const arr = [1, 2, 3, 4, 5]
// 从下标2开始删除1位元素,返回一个数组,该数组包含删除的元素
console.log(arr.splice(2, 1)) // [3]
console.log(arr) // [1,2,4,5]4.3 shift()删除首个元素
const arr = [1, 2, 3, 4, 5]
// shift()用于删除数组的首个元素并返回被删除的元素
console.log(arr.shift()) // 14.4 pop()删除数组最后一个元素
const arr = [1, 2, 3, 4, 5]
// pop()用于删除数组最后一个元素并返回被删除的元素
console.log(arr.pop()) // 5
console.log(arr) // [1,2,3,4]5.判断是否是数组的4种方式
5.1 通过JSON.stringify()将数组转换为字符串,判断是否是数组
JSON.stringify() 是 JavaScript 中一个非常重要的内置方法,用于将 JavaScript 值转换为 JSON 字符串,可以通过JSON.stringify()将数组转换为字符串, 如果转换后的第一个字符串是"["那么就说明是数组,否则就不是数组。注意:这种方式在循环引用问题下会报错,不推荐使用。
const arr1 = [1, 3, 4, 5]
const isArr = JSON.stringify(arr1)[0] === '['
console.log(isArr) // true
// 注意:在出现循环引用场景下,JSON.stringify()会报错,不推荐使用
const arr2 = [1, 3, 5, {}, 4]
arr2[3].arr2 = arr2
console.log(JSON.stringify(arr2)) // 报错:Converting circular structure to JSON5.2 通过instanceof判断是否是数组
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,如果实例对象的原型链上有构造函数的 prototype 属性,那么 instanceof 运算符就会返回 true,否则返回 false。注意:instanceof 运算符只能用于检测对象是否是数组,不能用于检测基本数据类型是否是数组。由于instanceof 运算符基于原型链判断,如果一旦改变目标对象的原型链,则可能导致判断错误。其次instanceof运算符也无法判断iframe.contentwindow.Array。
const arr1 = []
const isArr1 = arr1 instanceof Array
console.log(isArr1) // true
// 注意:instanceof 运算符只能用于检测对象是否是数组,不能用于检测基本数据类型是否是数组。一旦改变目标对象的原型链,则可能导致判断错误
const arr2 = {}
// 改变arr2的原型为Array.prototype,会导致instanceof 运算符判断错误
Object.setPrototypeOf(arr2, Array.prototype)
const isArr2 = arr2 instanceof Array
console.log(isArr2) // true
/**
* 注意:instanceof 无法 iframe.contentwindow.Array,由于window.Array 是一个全局对象,而 iframe.contentwindow.Array
* 是一个 iframe 中的数组对象,它们的原型链不同,因此 instanceof 运算符无法判断 iframe.contentwindow.Array 是否是数组。
*/
const Array1 = window.Array
const iframe = document.querySelector('iframe')
const Array2 = iframe.contentwindow.Array
console.log(Array1 === Array2) // false
const arr = new Array2()
console.log(arr instanceof Array) // false5.3 Object.prototype.toString.call()判断是否是数组
Object.prototype.toString 是 JavaScript 中所有对象都继承的方法,它返回一个表示对象类型的字符串。调用 Object.prototype.toString 方法时,会返回一个字符串,该字符串的格式为"[object 类型]",其中类型是对象的类型,例如"Array","Object","String"等,在ES5前Object.prototype.toString是精确判断类型的唯一方式。注意:在ES6标准中,提供了Symbol.toStringTag,它是一个内置的 Symbol 值,允许自定义 Object.prototype.toString.call() 方法返回的字符串中的类型标签,因此Object.prototype.toString.call()无法精确判断类型。
const arr1 = []
console.log(Object.prototype.toString.call(arr1)) // '[object Array]'
// 注意:Symbol.toStringTag 可以自定义对象的类型标签,因此 Object.prototype.toString.call() 无法精确判断类型
const arr2 = {
[Symbol.toStringTag]: 'Array',
}
console.log(Object.prototype.toString.call(arr2)) // '[object Array]'5.4 通过Array.isArray()判断是否是数组
Array.isArray() 是 JavaScript 中专门用于判断一个值是否为数组的静态方法,它是一个原生方法,底层由C++实现,可以精确判断是否是数组。
const arr1 = []
console.log(Array.isArray(arr1)) // true6.数组去重
6.1 使用 set 不允许重复元素的特性
var arr = [1, 2, 3, 4, 1, 2]
console.log(Array.from(new Set(arr))) // [1,2,3,4]6.2 创建一个新数组,使用 indexOf()includes()或判断新数组是否存在指定元素,如果不存在就添加元素
var arr = [1, 2, 3, 4, 1, 2]
// indexOf()写法
var newArr = []
for (let i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) == -1) {
newArr.push(arr[i])
}
}
console.log(newArr) // [1, 2, 3, 4]
// includes()写法
var newArray = []
for (let i = 0; i < arr.length; i++) {
if (!newArray.includes(arr[i])) {
newArray.push(arr[i])
}
}
console.log(newArray) // [1, 2, 3, 4]6.3 通过对象键名不允许重复的特性去重
var arr = [1, 2, 3, 4, 1, 2]
var obj = {}
var objArr = []
for (let i = 0; i < arr.length; i++) {
//如果obj不存在该键的话就往新数组添加元素
if (!obj[arr[i]]) {
obj[arr[i]] = 1
objArr.push(arr[i])
}
}
console.log(objArr) //[1, 2, 3, 4]6.4 利用 filter 去重
var arr = [1, 2, 3, 4, 1, 2]
console.log(arr.filter((curValue, index, array) => array.indexOf(curValue) === index)) // [1, 2, 3, 4]7.数组相关题目
7.1 已知如下数组,编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组。
var arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]解法一:先使用 flat() 将数组拍平,然后数组转 Set 去重,然后通过 Array.from() 将对象转为数组,最后通过 sort()升序排序
const arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]
console.log(Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b))
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]解法二:通过递归拍平数组,然后转 Set 去重,最后 sort()升序
const arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]
Array.prototype.flat = function () {
return [].concat(...this.map(item => (Array.isArray(item) ? item.flat() : [item])))
}
Array.prototype.unique = function () {
return [...new Set(this)]
}
const sort = (a, b) => a - b
console.log(arr.flat().unique().sort(sort))
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]8.数组技巧
compose(组合)函数和 pipe(管道)函数属于函数式编程(FP)中的概念,函数式编程中,组合函数是一种将多个函数组合在一起形成一个新函数的技术,管道是函数式编程的一种编程模式,它允许将函数链接在一起以构建数据流,两者都可以组织多个函数执行,其区别如下:
- 接收参数不同。管道函数接收一个初始值和一组函数,它会依次将初始值传入每个函数,最终输出管道的最后一个函数的结果。而组合函数接收一组数组,并将多个函数组合成一个新的函数,组合函数会将每个函数的输出作为上一个函数的输入,最终输出组合函数的结果。
- 函数调用顺序不同。管道函数是从左到右依次调用每个函数,而组合函数是从右到左依次调用每个函数。
8.1 通过 reduce()实现 pipe(管道)函数
const pipe = function (value, ...fns) {
return fns.reduce((result, fn) => {
return fn(result)
}, value)
}
// -------- 测试
function addOne(num) {
return num + 1
}
function double(num) {
return num * 2
}
const result = pipe(3, addOne, double)
console.log(result) // 88.2 通过 reduceRight() 或 reduce()实现 compose(组合)函数
// 实现方式1:基于reduceRight实现compose函数,reduceRight()由右到左遍历数组
const compose = function (...fns) {
return args => {
return fns.reduceRight((result, fn) => {
return fn(result)
}, args)
}
}
// 实现方式2:基于reduce()实现compose函数,从里到外求值,即从右到左求值
const compose = function (...fns) {
return fns.reduce((result, fn) => {
return (...args) => result(fn(...args))
})
}
// -------- 测试
function addOne(num) {
return num + 1
}
function double(num) {
return num * 2
}
const composeFn = compose(double, addOne)
console.log(composeFn(3)) // 89.数组与树形结构转换
在实际开发中由于存储数据时采用线性结构,但展示数据时以树形结构显示,例如菜单列表,数据库以行的形式存储着菜单项,但是展示数据时需要根据菜单项的层级关系转为树形结构显示,因此需要实现数组和树形结构的互转。数组结构如下:
const nodes = [
{ id: 1, parentId: null },
{ id: 2, parentId: 1 },
{ id: 3, parentId: 1 },
{ id: 4, parentId: 2 },
{ id: 5, parentId: 3 },
{ id: 6, parentId: 3 },
{ id: 7, parentId: 4 },
{ id: 8, parentId: 4 },
]9.1 数组转树形结构
9.1.1 使用递归数组转树
function arrayToTree(nodes, parentId = null) {
return nodes
.filter(node => node.parentId === parentId)
.map(node => ({ ...node, children: arrayToTree(nodes, node.id) }))
}递归是处理树形结构最常用的方法之一,可以通过递归遍历数组并构建树形结构。递归方法需要定义一个递归函数,该函数接受一个数组和一个父节点 ID 作为参数,并返回一个树形结构。递归函数的实现过程中,需要遍历数组,找到所有父节点 ID 等于当前父节点 ID 的节点,并将其添加到当前节点的子节点列表中。然后,递归调用函数,将其子节点添加到当前节点中。最后,返回树形结构。
递归方式实现数组转树比较简单,其时间复杂度是 O(n^2),n 表示节点的数量,空间复杂度为 O(n^2)。递归方式仅使用小规模数据,处理大规模数据时可能会导致栈溢出。
9.1.2 使用迭代数组转树
迭代方法也可以用于将数组转换为树形结构,但是实现起来比递归方法更复杂。遍历数组找到所有父节点 ID 等于当前节点 ID 的节点,并将其添加到当前节点的子节点列表中。
function arrayToTree(nodes) {
const map = new Map()
const tree = []
for (const node of nodes) {
// 初始化map,其key为节点id,value为节点,根据节点id可以快速查找节点
map.set(node.id, node)
// 处理外层节点,如果节点的parentId为null,则直接将该节点push到tree中
if (node.parentId === null) {
tree.push(node)
} else {
// 根据节点的parentId查找节点对应的父节点
const parent = map.get(node.parentId)
// 初始化children属性
!parent.children && (parent.children = [])
// 将当前节点push到父节点的children中
parent.children.push(node)
}
}
return tree
}迭代实现数组转树时间复杂度为 O(n),其中 n 是节点的数量,空间复杂度为 O(n)。此种方式使用于适合大规模数据,相较于递归实现比较复杂。
9.1.3 使用 reduce 数组转树
function arrayToTree(nodes) {
const map = {}
const tree = nodes.reduce((acc, node) => {
// 初始化map,以node的id为key
map[node.id] = { ...node, children: [] }
// 处理最外层节点
if (node.parentId === null) {
acc.push(map[node.id])
} else {
// 处理内层节点
map[node.parentId].children.push(map[node.id])
}
return acc
}, [])
return tree
}reduce 与迭代方式类似,但实现更为简洁,其时间复杂度为 O(n),n 表示节点的数量,空间复杂度为 O(n),适用于中小规模数据。
9.1.4 使用哈希表数组转树
function arrayToTree(nodes) {
// 初始化map,map的key为node.id,value为node的所有属性和chidrent属性组成的对象
const map = new Map(nodes.map(node => [node.id, { ...node, children: [] }]))
const tree = []
// 遍历map的value集合
for (const node of map.values()) {
// 处理外层节点
if (node.parentId === null) {
tree.push(node)
} else {
// 处理内层节点
map.get(node.parentId).children.push(node)
}
}
return tree
}使用 Map 方法可以将数组转换为 Map,然后通过遍历 Map 来构建树形结构。具体实现过程中,需要先将数组转换为 Map,其中键为节点 ID,值为节点对象。然后,遍历 Map 找到所有父节点 ID 等于当前节点 ID 的节点,并将其添加到当前节点的子节点列表中。最后,返回根节点。
map 实现数组转树的时间复杂度为 O(n),其中 n 是节点的数量,空间复杂度为 O(n)。map 方式适合大规模数据,而且由于使用了 Map,相比于迭代方式,能够更方便地进行节点的查找和删除。
9.1.5 使用深度优先搜索(DFS)数组转树
function arrayToTreeDFS(nodes) {
// 初始化map,map的key为node.id,value为node的所有属性和chidrent属性组成的对象
const map = new Map(nodes.map(node => [node.id, { ...node, children: [] }]))
const tree = []
// 遍历map的value集合
for (const node of map.values()) {
// 处理外层节点
if (node.parentId === null) {
dfs(node)
tree.push(node)
}
}
// 深度优先搜索,调用该函数后,会根据parentId获取对应节点,并做为node的子节点
function dfs(node) {
// 遍历所有节点
for (const child of nodes) {
// 如果当前节点的父id === 传入节点的id
if (child.parentId === node.id) {
// 根据child.id 从map获取节点
const childNode = map.get(child.id)
dfs(childNode)
// 将childNode作为node的子节点
node.children.push(childNode)
}
}
}
return tree
}基于 DFS 实现的数组转树时间复杂度为 O(n),其中 n 是节点的数量,空间复杂度为 O(n)。这种方式适用于大规模数据的处理,而且可以更方便地进行深度优先搜索。
9.2 树结构转数组
树结构转数组:
前序遍历方式:使用前序遍历(根-左-右)的方式遍历树,将节点的值按照遍历顺序存储到数组中。这种方式可以方便地将树转换为数组,但在还原树结构时需要额外的信息(如节点的子节点数量或特定的占位符)来确定节点的层次关系。
层次遍历方式:使用层次遍历(逐层从左到右)的方式遍历树,将节点的值按照层次顺序存储到数组中。这种方式可以直观地将树结构转换为数组,而且在还原树结构时不需要额外的信息,只需按照层次顺序逐个构建节点。
9.1 前序遍历
const root = [
{
id: 1,
parentId: null,
children: [
{
id: 2,
parentId: 1,
children: [
{
id: 4,
parentId: 2,
children: [
{
id: 5,
parentId: 4,
children: [],
},
{
id: 6,
parentId: 4,
children: [
{
id: 8,
parentId: 6,
children: [],
},
],
},
],
},
],
},
{
id: 3,
parentId: 1,
children: [
{
id: 7,
parentId: 3,
children: [],
},
],
},
],
},
]
function treeToArrayPreorder(root) {
const result = []
function traverse(node) {
if (node) {
result.push(node)
for (const child of node.children) {
traverse(child)
}
}
}
root.forEach(node => {
traverse(node)
})
return result
}9.2 层次遍历
function treeToArrayLevelOrder(root) {
const queue = [...root]
const result = []
while (queue.length > 0) {
const node = queue.shift()
result.push(node)
for (const child of node.children) {
queue.push(child)
}
}
return result
}