[JS] Table 轉 Object 練習 part2

by Mesak

前情提要? 上一篇實作方法利用了三個迴圈,取得標題,將標題的資料利用索引(index) 抓取表格的內容,

程式碼如下:

let headData = Array.from(document.querySelectorAll('thead > tr > th')).map(n=>n.innerText)
console.log('headData', headData )

let tableObject = []
for(let trNode of document.querySelectorAll('tbody > tr') ){
  let data = {};
  Array.from(trNode.querySelectorAll('td')).forEach((n , index)=>{
    data[ headData[index] ] = n.innerText
  })
  tableObject.push(data)
}
console.log( 'tableObject ',tableObject )

其中內容可以看到,第二段的地方,所跑的迴圈是針對 td 來做處理,headData 的部分其實也是針對 tr 下的 欄位去做處理,以前針對 XHTML 有稍微看一下規範,在標準的 HTML 結構下 TABLE 的子節點必須是 caption,colgroup,thead,tbody,tr 幾種節點,tr 這個節點下也只能有 th 跟 td 在這個標準規範下,我們理論上可以信任瀏覽器會解析這些元素來使用

所以現在要做的事情就是 拿出 tr 下的 子節點,在查詢 MDN 文件的時候,可以看到文件底部有一個 Specifications ,引導到 html.spec 網站去,這網站制定了 HTML 的規則,在這邊可以查到標準的文件,雖然很難閱讀…

不過我利用 console.dir( 把 trNode) 給印出來,比照規格書發現了 tr.cells 的屬性,裡面放了允許的子元素 th跟td ,而且也不分標籤內容,因此這邊就可以快速的簡化 tr 下的目標

let headData = Array.from(document.querySelector('thead > tr').cells).map(n=>n.innerText)

這樣標題乍看沒太大改變,但實際上後面會有另外的用途

let tableObject = []
for(let trNode of document.querySelectorAll('tbody > tr') ){
  console.dir(trNode)
  let tdData = Array.from(trNode.cells).map(n=>n.innerText)
  
  tableObject.push(tdData)
}

第二段把 td的所有資料轉成跟 headData的類型一樣,的陣列數值,這樣兩個方法就做一樣的事情,後面就可以一併簡化

但是 tdData 要如何把陣列加入 KEY 變成物件 push 到 tableObject 呢?google “陣列轉換為物件” 可以找到一個 方法 reduce ,reduce 一開始有點難以理解,這邊推薦一個小工具 https://pythontutor.com ,連進去貼上一個簡單的範例,

let head = ["Company","Contact","Country"];
let data = [
   [1,2,3],
   [4,5,6]  
];
data.forEach( (oneRaw) => {
  let s = oneRaw.reduce( (newData,itemData)=>{
    console.log( newData,itemData )
    return newData
  },{})
  console.log(s)
})

這段程式碼最大化模擬運作的變數變化數值等等資料,基本上就是 123 本身是一個陣列,對應 td 的資料,456 亦同,123跟456都屬於另一個變數 data 二維陣列中的陣列,所以把他們跑過一次,根據 console.log 的顯示結果 newData 會根據 return 一直傳遞到下一個 newData,itemData,會傳遞他的陣列中的數值,reduce 在傳遞的時候有三個參數值,可以利用這個參數值取得目前 array 的 index 數值,所以要讓他可以動的程式碼如下:

let head = ["Company","Contact","Country"];
let data = [
   [1,2,3],
   [4,5,6]  
];
data.forEach( (oneRaw) => {
  let s = oneRaw.reduce( (newData,itemData,index)=>{
    newData[ head[index] ] = itemData
    return newData
  },{})
  console.log(s)
})

一樣 給索引值之後利用 head 的 value 當成 key值,最後給予 數值

測試完了以後就可以引用到程式碼中

let tableObject = []
for(let trNode of document.querySelectorAll('tbody > tr') ){
  console.dir(trNode)
  let tdData = Array.from(trNode.cells).map(n=>n.innerText)
  let tdItem =  oneRaw.reduce( (newData,itemData,index)=>{
    newData[ head[index] ] = itemData
    return newData
  },{})
  tableObject.push(tdItem )
}

剛剛有提到兩個重複的 function 方法我們可以合併一下內容,最後把不需要的變數整理一下

function trNodeToArray( trNode ){
  return Array.from(trNode.cells).map(n=>n.innerText)
}

let headData = trNodeToArray( document.querySelector('thead > tr') )
console.log('headData', headData )


let tableObject = []

for(let trNode of document.querySelectorAll('tbody > tr') ){
  console.dir(trNode)
  let tdData = trNodeToArray(trNode).reduce( (newData,itemData,index)=>{
    newData[ headData[index] ] = itemData
    return newData
  },{})
  tableObject.push( tdData )
}
console.log( 'tableObject ',tableObject )

最後雖然把一樣的方法抽離出來了,但是看起來很怪,如何能確定 抽離出來使用這個 trNodeToArray 的物件塞入的參數就是 tr 節點呢?這個時候就需要原型鏈的用法…

You may also like