前幾天遇到一個樹型組件(類似樹形菜單)資料格式化的問題,由於後台把原始查詢的資料直接返回給前端,父子關係並未構建,因此需要前端JS來完成,後台返回的資料和下面的測試資料相似。
var data=[{id:1,pid:0,text:'A'},{id:2,pid:4,text:"E[父C]"},{id:3,pid:7,text:"G[父F]"},{id:4,pid:1,text:"C[父A]"},{id:5,pid:6,text:"D[父B]"},{id:6,pid:0,text:'B'},{id:7,pid:4,text:"F[父C]"}];
我們可以發現上面的測試資料有幾個特點,父節點與子節點不是順序排列的,也就是說按照id的順序,並不是先有父節點,然後有下面的子節點,順序是混亂的,再就是父子層級有很多,這裡是3層。總結為:順序混亂,層級未知。
如果是順序排列,層級固定,可以投機取巧,寫法相對簡單,但是這裡恰恰相反。因此給格式化造成了一定的困難,當遇到層級未知的時候,一般都會想到遞迴的寫法,這裡我感覺用遞迴也不好做,因此我也就沒有向這方面去深入思考,這裡就也不做多的說明。
那麼我的做法比起遞迴來講更容易理解,先看下代碼。
function toTreeData(data){var pos={};var tree=[];var i=0;while(data.length!=0){if(data[i].pid==0){tree.push({id:data[i].id,text:data[i].text,children:[]});pos[data[i].id]=[tree.length-1];data.splice(i,1);i--;}else{var posArr=pos[data[i].pid];if(posArr!=undefined){var obj=tree[posArr[0]];for(var j=1;j<posArr.length;j++){obj=obj.children[posArr[j]];}obj.children.push({id:data[i].id,text:data[i].text,children:[]});pos[data[i].id]=posArr.concat([obj.children.length-1]);data.splice(i,1);i--;}}i++;if(i>data.length-1){i=0;}}return tree;}
前面的測試資料經過上面代碼中的方法格式化後如下:
[ { "id": 1, "text": "A", "children": [ { "id": 4, "text": "C[父A]", "children": [ { "id": 7, "text": "F[父C]", "children": [ { "id": 3, "text": "G[父F]", "children": [] } ] }, { "id": 2, "text": "E[父C]", "children": [] } ] } ] }, { "id": 6, "text": "B", "children": [ { "id": 5, "text": "D[父B]", "children": [] } ] }]
原理很簡單,使用一個死迴圈來遍曆數組,迴圈跳出的條件是數組的長度為0,也就是說,迴圈內部會引起數組長度的改變。這裡就幾個關鍵點做一下說明。 為什麼要用死迴圈。順序混亂,如果單次迴圈,子節點出現在父節點之前,子節點不好處理,這裡做一個死迴圈相當於先把父節點全部找出,但是這裡又不是簡單的先把所有的父節點找出,找的同時,如果這個節點父節點已經找到,那麼可以繼續做後續操作; 如何建立層級關係。代碼中有一個變數pos,這個用於儲存每個已添加到tree中的節點在tree中位置資訊,比如上面測試資料父節點A添加到tree後,那麼pos中增加一條資料,pos={”1“:[0]},1就是父節點A的id,這樣寫便於尋找,[0]表示父節點A在tree的第一個元素,即tree[0],如果某個位置資訊為[1,2,3],那麼表示這個節點在tree[1].children[2].children[3],這裡的位置關係其實就是父子的層級關係。
上面的測試資料的pos資訊如下:
{"1":[0],"2":[0,0,1],"3":[0,0,0,0],"4":[0,0],"5":[1,0],"6":[1],"7":[0,0,0]}