詳解JavaScript如何優雅地實現創建多維數組

前言

已經堅持力扣刷題 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多維數組內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: