前兩天,幫朋友解決一個問題:node
ajax請求獲得的數據,是一個對象數組,每一個對象中,具備三個屬性,parentId,id,name,而後根據這個數據生成對應的結構。git
恰好最近在看React,而且瞭解到其中的虛擬DOM,其實,就是利用json數據來代替DOM結構表示,而後利用這個json數據,渲染出DOM樹,整體添加到頁面中。下面,我就經過介紹我如何實現上面實際問題的思路,一邊完成實際需求,一邊實現React中虛擬DOM渲染成DOM的原理。github
1 var allJson = [{ 2 'id': '1', 3 'name': '我是1 我是根節點..個人長度是..' 4 }, { 5 'id': '2', 6 'parentId': '1', 7 'name': '我是2 個人父級是1..個人長度是..' 8 }, { 9 'id': '3', 10 'parentId': '2', 11 'name': '我是3 個人父級是2...個人長度是..' 12 }, { 13 'id': '8', 14 'parentId': '4', 15 'name': '我是8 個人父級是4..個人長度是..' 16 }, { 17 'id': '4', 18 'parentId': '2', 19 'name': '我是4 個人父級是2..個人長度是..' 20 }, { 21 'id': '5', 22 'parentId': '3', 23 'name': '我是5 個人父級是3..個人長度是..' 24 }, { 25 'id': '6', 26 'parentId': '1', 27 'name': '我是6 個人父級是1..個人長度是..' 28 }, { 29 'id': '7', 30 'parentId': '4', 31 'name': '我是7 個人父級是4..個人長度是..' 32 }];
1)建立一個數組childRoot,用於存放已經添加到DOM樹中的對象。(其中每個元素,均可能有子節點),建立數組cloneAllJson,用於存放原始數據複製。ajax
2)利用根節點沒有parentId,便利全部節點,找到根節點,並將該節點對象從cloneAllJson數組對象中移除(減小重複遍歷)。json
3)循環childRoot數組,在數組剩下的節點對象中,根據parentId找到childRoot數組中的當前第一個元素的全部的子節點,每找到一個,直接添加到DOM樹中,並添加到childRoot數組中,且從cloneAllJson數組中移除找到的子節點。添加完全部子節點後,移除childRoot的第一個元素。如此循環,直到childRoot數組長度爲零。數組
代碼以下: 數據結構
1 // 模擬數據 2 var allJson = [{ 3 'id': '1', 4 'name': '我是1 我是根節點..個人長度是..' 5 }, { 6 'id': '2', 7 'parentId': '1', 8 'name': '我是2 個人父級是1..個人長度是..' 9 }, { 10 'id': '3', 11 'parentId': '2', 12 'name': '我是3 個人父級是2...個人長度是..' 13 }, { 14 'id': '8', 15 'parentId': '4', 16 'name': '我是8 個人父級是4..個人長度是..' 17 }, { 18 'id': '4', 19 'parentId': '2', 20 'name': '我是4 個人父級是2..個人長度是..' 21 }, { 22 'id': '5', 23 'parentId': '3', 24 'name': '我是5 個人父級是3..個人長度是..' 25 }, { 26 'id': '6', 27 'parentId': '1', 28 'name': '我是6 個人父級是1..個人長度是..' 29 }, { 30 'id': '7', 31 'parentId': '4', 32 'name': '我是7 個人父級是4..個人長度是..' 33 }, ]; 34 35 /* 複製數據 */ 36 var cloneAllJson = allJson.concat(); 37 38 /* 找到根節點ID 並畫出根節點到頁面 */ 39 /* 定義一個數組用來裝每次新生成的次級父節點 */ 40 var childRoot = []; 41 /* 遍歷全部的接節點,查找根節點 */ 42 for (var i = 0; i < allJson.length; i++) { 43 /* 若是不存在父節點字段 則爲根節點 */ 44 if (allJson[i].parentId == undefined) { 45 /* 賦值根節點ID */ 46 rootId = allJson[i].id; 47 /* 將根節點添加到childRoot數組中,而後在複製的數組中刪除這個根節點 */ 48 childRoot.push(allJson[i]); 49 cloneAllJson.splice(i, 1); 50 /* 畫出根節點 */ 51 var div = document.createElement('div'); 52 div.id = allJson[i].id; 53 div.appendChild(document.createTextNode(allJson[i].name)); 54 document.getElementById('rootId').appendChild(div); 55 } 56 } 57 58 59 /*方法一:每次找到父節點的全部子節點,添加到dom樹上, 60 * 並添加到childRoot數組中,從clone數組中剔除,(減小重複遍歷) 61 * 直到childRoot數組中長度爲0 62 * 能夠解決,json數組中,父節點在子節點後面的問題 63 */ 64 while (childRoot.length) { 65 /* 遍歷cloneAllJson數組,找到childRoot第一個元素的全部子節點,並直接添加到頁面上*/ 66 for (var i = 0; i < cloneAllJson.length; i++) { 67 if (cloneAllJson[i].parentId == childRoot[0].id) { 68 /* 畫出一級子節點 */ 69 var div = document.createElement('div'); 70 div.id = cloneAllJson[i].id; 71 div.appendChild(document.createTextNode(cloneAllJson[i].name)); 72 /* 直接添加到頁面上 */ 73 document.getElementById(childRoot[0].id).appendChild(div); 74 /* 將該節點添加到childRoot中,以後遍歷添加其子節點 */ 75 childRoot.push(cloneAllJson[i]); 76 /* 將該節點從cloneAllJson數組中刪除,並將索引向後減1 */ 77 cloneAllJson.splice(i, 1); 78 i--; 79 } 80 } 81 /* 從childRoot數組中移除第一個元素(已經將其全部孩子添加到頁面中) */ 82 childRoot.shift(); 83 }
最終生成dom結構以下圖顯示:app
其中rootId,是咱們本身添加外節點。dom
其實,在這個過程當中,我強調了,每次找到節點,直接添加到頁面上,在添加以前,都是先根據id查找父節點,其實,DOM操做性能不好,通常都是儘可能減小DOM操做。在這裏,咱們就能夠利用React中虛擬DOM渲染到頁面上的方法了。函數
其實主要思想仍是方法一的思想,惟一不一樣,就是咱們不是直接把節點對象添加到頁面結構中,而是,給其父節點添加一個childObjs屬性(用於存放全部子節點對象的數組)中。而後再利用遞歸,將全部節點渲染到頁面上。其中,咱們只進行了一次DOM查找操做,即最終調用render函數時。
代碼以下:
1 /* 模擬數據 */ 2 var allJson = [{ 3 'id': '1', 4 'name': '我是1 我是根節點..個人長度是..' 5 }, { 6 'id': '2', 7 'parentId': '1', 8 'name': '我是2 個人父級是1..個人長度是..' 9 }, { 10 'id': '3', 11 'parentId': '2', 12 'name': '我是3 個人父級是2...個人長度是..' 13 }, { 14 'id': '8', 15 'parentId': '4', 16 'name': '我是8 個人父級是4..個人長度是..' 17 }, { 18 'id': '4', 19 'parentId': '2', 20 'name': '我是4 個人父級是2..個人長度是..' 21 }, { 22 'id': '5', 23 'parentId': '3', 24 'name': '我是5 個人父級是3..個人長度是..' 25 }, { 26 'id': '6', 27 'parentId': '1', 28 'name': '我是6 個人父級是1..個人長度是..' 29 }, { 30 'id': '7', 31 'parentId': '4', 32 'name': '我是7 個人父級是4..個人長度是..' 33 }]; 34 35 /* 數據複製 */ 36 var cloneAllJson = allJson.concat(); 37 38 /* 根節點對象 */ 39 var root = {}; 40 /* 定義一個數組用來裝每次新生成的父節點 */ 41 var childRoot = []; 42 43 /* 查找根節點,並記錄根節點,並將該節點從數組中剔除 */ 44 cloneAllJson.forEach(function(node, index) { 45 /* 不存在parentId,就是根節點root */ 46 if (!node.parentId) { 47 /* 引入深度,方便後期控制樣式 */ 48 node.deep = 1; 49 root = node; 50 childRoot.push(root); 51 cloneAllJson.splice(index, 1); 52 } 53 }); 54 55 /* 給全部childRoot節點中childObj中添加其子節點 */ 56 while (childRoot.length) { 57 let parent = childRoot[0]; 58 for (let j = 0; j < cloneAllJson.length; ++j) { 59 let node = cloneAllJson[j]; 60 if (node.parentId == parent.id) { 61 node.deep = parent.deep + 1; 62 /* 引入childObjs,用於存放全部子節點對象的數組 */ 63 if (!parent.childObjs) { 64 parent.childObjs = []; 65 66 } 67 parent.childObjs.push(node); 68 childRoot.push(node); 69 cloneAllJson.splice(j--, 1); 70 } 71 } 72 childRoot.shift(); 73 } 74 75 console.log(root); 76 77 /* 渲染函數 */ 78 function render(node, root) { 79 var elem; 80 /* 若是節點存在子節點對象,建立該節點,並遞歸調用渲染函數,將其渲染爲該節點的子元素 */ 81 /* 不然:直接渲染該節點*/ 82 if (node.childObjs) { 83 var elem = createNode(node); 84 node.childObjs.forEach(function(item) { 85 render(item, elem); 86 }); 87 } else { 88 var elem = createNode(node); 89 } 90 /* 添加到頁面中的節點上 */ 91 root.appendChild(elem); 92 }) 93 94 // 建立節點工廠函數 95 function createNode(node) { 96 var div = document.createElement('div'); 97 div.style.paddingLeft = 20 + 'px'; 98 div.style.fontSize = 16 - node.deep + 'px'; 99 div.appendChild(document.createTextNode(node.name)); 100 return div; 101 }
最終顯示結果截圖:
其實準確的說,我一共寫了四種實現方法,可是這兩種,是其中最好簡單的兩種,但願你們批評指正。
github上函數地址:https://github.com/DiligentYe/my-frame/blob/master/json-to-dom.js