本文深入探讨JavaScript中创建多维数组的多种方法,从基础的for循环、map到Array.from,并重点介绍如何通过递归函数和Proxy对象实现更优雅、类似强类型语言的数组定义方式,帮助开发者提升代码简洁性与可读性。
前言
在坚持进行LeetCode算法刷题的80天里,频繁涉及到多维数组的构建操作。
例如,面对以下两个具体需求:
- 构建一个尺寸为 10 * 10 的数组,所有元素初始值设为 0。
- 构建一个尺寸为 10 * 10 的数组,元素值范围在 0 到 99 之间。
大家通常会采用什么方式来创建这两个数组呢?
常规方法
这里展示三种常见的创建多维数组的手段。
for 遍历
最经典且直观的方法是使用 for 循环。
以下是满足第一个需求的代码实现:
const arr = []
for (let i = 0; i < 10; i++) {
arr[i] = []
for (let j = 0; j < 10; j++) {
arr[i][j] = 0
}
}若要实现第二个需求,只需将 arr[i][j] = 0 替换为 arr[i][j] = i * 10 + j 即可。
map
我个人更倾向于使用 map 方法来创建数组。
针对第一个需求:
const arr1 = new Array(10).fill(0).map(() => new Array(10).fill(0))针对第二个需求:
const arr2 = new Array(10).fill(0).map((_, i) => new Array(10).fill(0).map((_, j) => i * 10 + j))由于 map 不会处理数组中的空位,因此创建数组后必须先用 fill 进行赋值。
Array.from
在浏览其他题解时,经常看到使用 Array.from 来初始化数组的做法。
Array.from 可以将类数组或可迭代对象转换为真正的数组,并可通过第二个参数传递函数来修改新数组的元素,其作用类似于 map。
所谓类数组,指的是拥有 length 属性的对象。
针对第一个需求:
const arr1 = Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => 0))针对第二个需求:
const arr2 = Array.from({ length: 10 }, (_, i) => Array.from({ length: 10 }, (_, j) => i * 10 + j))不足
上述三种方法中,for 遍历代码量较大,map 和 Array.from 在复杂场景下可读性稍差。
相比之下,其他强类型语言声明多维数组更为便捷,语法简洁明了。
int arr[10][10]在强类型语言中,int 类型数组的默认初值为 0,若需实现第二个需求,仍需额外处理。
因此,我尝试实现一个函数,能够简单明了地创建并设置多维数组的初值。
递归函数实现
首先明确函数的具体需求。
fun(0, false, 10) // 创建一个长度为10,初值为0的数组
fun(0, true, 10, 10) // 创建一个10 * 10的数组,初值为0-99设计函数接收的参数如下:
1. 数组的初值。
2. 设置初值时是否需要递增。
3. 剩余参数,表示每个维度的数组长度。
代码实现并不复杂,直接展示如下:
若为一维数组,直接赋值,赋值时判断是否需要递增。
若是多维数组,则递归创建,在需要递增时需计算传入的初值。
function fun(value, inc, ...dimensions) {
// 取首元素,创建数组
const length = dimensions.shift()
const arr = new Array(length)
if (dimensions.length == 0) {
// 一维数组 赋值
for (let i = 0; i < length; i++) {
arr[i] = inc ? value++ : value
}
} else {
// 多维数组 递归创建
let gap = dimensions.reduce((a, b) => a * b, 1) // 初值的间隔
for (let i = 0; i < length; i++) {
arr[i] = fun(value + (inc ? i * gap : 0), inc, ...dimensions)
}
}
return arr
}链式函数实现
失败的尝试
实际上,我个人对先传递初值的方式略感不适。
原本计划创建一个链式函数,使其语法与其他语言定义多维数组的方式一致并进行改造升级。
fun(10) // 创建一个长度为10,不设置初值
fun(10)(10, 0) // 创建一个10 * 10的数组,初值为0
fun(10)(10)(10, 0, true) // 创建一个10 * 10的数组,初值为0-999但遇到一个问题:fun 的返回值是一个数组,但它又需要作为函数继续调用。
我尝试通过设置原型的方式让函数具备数组的功能。
Object.setPrototypeOf(fun, Array.prototype)然而,函数自带不可配置的 length 属性,作为数组无法正确读取 length,导致无法正常使用。
处理办法
要解决这个返回值的问题,只能根据参数进行区分,判断是返回函数还是返回数组。
- 当传入数字为正数时,视为长度,处理数组并返回函数自身。
- 当传入数字为非正数时,视为初值,将之前处理的数组赋值并返回。
在数组处理时运用了一些技巧,将树形的数组结构铺平,代码如下:
let root = [] // 根数组 root[0] 将会是返回的结果
let tile = [root] // 平铺数组
// 函数实现
function fun(length) {
if (length <= 0) {
// 结束标志 非正数
try {
// 空属性会被JSON转换成null,然后再替换为初值
let res = JSON.stringify(root[0])
res = res.replaceAll('null', Math.abs(length))
return JSON.parse(res)
} finally {
// 恢复变量
root = []
tile = [root]
}
} else {
const next = [] // 新的平铺数组
// 遍历平铺数组,里面的元素是倒数第二层
for (const two of tile) {
// 首次调用two.length为0,但需要执行一次
for (let i = 0; i < (two.length || 1); i++) {
const one = new Array(length) // 最后一层
two[i] = one
next.push(one)
}
}
// 更替平铺数组
tile = next
}
return fun
}
// 函数调用
fun(10)(0) // [0,0,0,0,0,0,0,0,0,0]
fun(2)(3)(-1) // [[1,1,1],[1,1,1]]
fun(2)(2)(2)(-2) // [[[2,2],[2,2]],[[2,2],[2,2]]]若需实现初值递增,只需再加一个参数或增加一种情况,此处留作读者自行实现。
使用 Proxy 实现
至此仍不满足,我们可以借助 Proxy,将函数调用改为属性访问,使其更接近强类型语言的习惯。
实现一个 int 代理对象:
let root = []
let tile = [root]
// 拦截器
const handers = {
get(target, key) {
key = parseInt(key)
if (key <= 0) {
try {
let res = JSON.stringify(root[0])
res = res.replaceAll('null', Math.abs(key))
return JSON.parse(res)
} finally {
root = []
tile = [root]
}
} else {
const next = []
for (const two of tile) {
for (let i = 0; i < (two.length || 1); i++) {
const one = new Array(key)
two[i] = one
next.push(one)
}
}
tile = next
}
return int
},
}
// 用 proxy 创建 int
const int = new Proxy({}, handers)至此,我们就可以像那些强类型语言一样,简单明了地定义多维数组了。
const arr = int[10][10][0] // 10*10 初值为 0 的数组到此这篇关于详解JavaScript如何优雅地实现创建多维数组的文章就介绍到这了,更多相关JavaScript多维数组内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!