前幾天遇到一個樹型組件(類似樹形菜單)數(shù)據(jù)格式化的問題,由于后臺把原始查詢的數(shù)據(jù)直接返回給前端,父子關(guān)系并未構(gòu)建,因此需要前端JS來完成,后臺返回的數(shù)據(jù)和下面的測試數(shù)據(jù)相似。
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]"}
];
我們可以發(fā)現(xiàn)上面的測試數(shù)據(jù)有幾個特點,父節(jié)點與子節(jié)點不是順序排列的,也就是說按照id的順序,并不是先有父節(jié)點,然后有下面的子節(jié)點,順序是混亂的,再就是父子層級有很多,這里是3層。總結(jié)為:順序混亂,層級未知。
如果是順序排列,層級固定,可以投機取巧,寫法相對簡單,但是這里恰恰相反。因此給格式化造成了一定的困難,當(dāng)遇到層級未知的時候,一般都會想到遞歸的寫法,這里我感覺用遞歸也不好做,因此我也就沒有向這方面去深入思考,這里就也不做多的說明。
那么我的做法比起遞歸來講更容易理解,先看下代碼。
function toTreeData(data){
var pos={};
var tree=[];
var i=0;
while(data.length!=0){
if(data[i].pid==0){
tree.push({ //頂層節(jié)點直接推入tree數(shù)組
id:data[i].id,
text:data[i].text,
children:[]
});
pos[data[i].id]=[tree.length-1]; //頂層節(jié)點保存其所在路徑數(shù)組,如 [0]
data.splice(i,1); //將當(dāng)前數(shù)據(jù)從data移除
i--;
}else{
var posArr=pos[data[i].pid]; //先獲取當(dāng)前節(jié)點的父節(jié)點位置數(shù)組, 如[0, 0, 0]
if(posArr!=undefined){ //如果存在父節(jié)點位置信息
var obj=tree[posArr[0]]; //父節(jié)點所在的頂層節(jié)點對象
for(var j=1;j<posArr.length;j++){
obj=obj.children[posArr[j]]; //遍歷找到tree中的父節(jié)點對象
}
obj.children.push({ //將當(dāng)前對象插入到父節(jié)點的children里
id:data[i].id,
text:data[i].text,
children:[]
});
pos[data[i].id]=posArr.concat([obj.children.length-1]); //依據(jù)父節(jié)點的位置數(shù)組去生成當(dāng)前節(jié)點的位置信息
data.splice(i,1); //將當(dāng)前對象從data中移除
i--;
}
}
i++;
if(i>data.length-1){ //如果i大于data.length,從頭開始繼續(xù)遍歷
i=0;
}
}
return tree;
}
前面的測試數(shù)據(jù)經(jīng)過上面代碼中的方法格式化后如下:
[
{
"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": []
}
]
}
]
原理很簡單,使用一個死循環(huán)來遍歷數(shù)組,循環(huán)跳出的條件是數(shù)組的長度為0,也就是說,循環(huán)內(nèi)部會引起數(shù)組長度的改變。這里就幾個關(guān)鍵點做一下說明。
- 為什么要用死循環(huán)?順序混亂,如果單次循環(huán),子節(jié)點出現(xiàn)在父節(jié)點之前,子節(jié)點不好處理,這里做一個死循環(huán)相當(dāng)于先把父節(jié)點全部找出,但是這里又不是簡單的先把所有的父節(jié)點找出,找的同時,如果這個節(jié)點父節(jié)點已經(jīng)找到,那么可以繼續(xù)做后續(xù)操作;
- 如何建立層級關(guān)系?代碼中有一個變量pos,這個用于保存每個已添加到tree中的節(jié)點在tree中位置信息,比如上面測試數(shù)據(jù)父節(jié)點A添加到tree后,那么pos中增加一條數(shù)據(jù),pos={”1“:[0]},1就是父節(jié)點A的id,這樣寫便于查找,[0]表示父節(jié)點A在tree的第一個元素,即tree[0],如果某個位置信息為[1,2,3],那么表示這個節(jié)點在tree[1].children[2].children[3],這里的位置關(guān)系其實就是父子的層級關(guān)系。
上面的測試數(shù)據(jù)的pos信息如下:
{
"1":[0],
"2":[0,0,1],
"3":[0,0,0,0],
"4":[0,0],
"5":[1,0],
"6":[1],
"7":[0,0,0]
}