相信作前端的小夥伴都有遇到過將一個平鋪的 ‘樹’ 結構轉換成一個真正的 ‘樹’ 結構,好比說下面這種:html
var _JSON_ = [ {id: 7, name: '豬', pid: 2}, {id: 8, name: '牛', pid: 2}, {id: 9, name: '羊', pid: 2}, {id: 13, name: '三黃雞', pid: 4}, {id: 14, name: '白羽雞', pid: 4}, {id: 15, name: '火雞', pid: 4}, {id: 4, name: '雞', pid: 1}, {id: 5, name: '鴨', pid: 1}, {id: 6, name: '鵝', pid: 1}, {id: 10, name: '粟', pid: 3}, {id: 11, name: '稻', pid: 3}, {id: 12, name: '黍', pid: 3}, {id: 1, name: '禽'}, {id: 2, name: '獸'}, {id: 3, name: '谷'} ];
最終要轉換成相似以下的格式,方便在頁面渲染:前端
[ {id: 1, name: '禽', pid: 0, children: [ {id: 4, name: '雞', pid: 1, children: [ {id: 13, name: '三黃雞', pid: 4}, {id: 14, name: '白羽雞', pid: 4}, {id: 15, name: '火雞', pid: 4} ]}, {id: 5, name: '鴨', pid: 1, children: []}, {id: 6, name: '鵝', pid: 1, children: []} ]}, {id: 2, name: '獸', pid: 0, children: [ {id: 7, name: '豬', pid: 2, children: []}, {id: 8, name: '牛', pid: 2, children: []}, {id: 9, name: '羊', pid: 2, children: []} ]}, {id: 3, name: '谷', pid: 0, children: [ {id: 10, name: '粟', pid: 3, children: []}, {id: 11, name: '稻', pid: 3, children: []}, {id: 12, name: '黍', pid: 3, children: []} ]} ]
你的方法是什麼樣的呢?思考中...jquery
相信有的小夥伴會是和網上大多數能搜到的答案同樣,用好幾個循環來實現,在這裏給你們解讀一下,我認爲看到代碼最少的一種解決方案,該方案出自FCC成都社區的水歌之手,Jsbin代碼地址:https://jsbin.com/budapagito/...git
//十一行的代碼實現將 ’平鋪的樹’ 轉換爲 ‘立體的樹’ 結構 function Array2Tree() { var TempMap = { }; $.each($.extend(true, [ ], arguments[0]), function () { var _This_ = TempMap[ this.id ]; _This_ = TempMap[ this.id ] = _This_ ? $.extend(this, _This_) : this; this.pid = this.pid || 0; var _Parent_ = TempMap[ this.pid ] = TempMap[ this.pid ] || { }; (_Parent_.children = _Parent_.children || [ ]).push(_This_); }); return TempMap[0].children; } console.log(JSON.stringify( Array2Tree(_JSON_), null, 4 ));
在看一段代碼時,咱們首先要了解裏面涉及到的知識點(從方法入口開始):數據庫
一、JSON.stringify(Array2Tree(_JSON_), null, 4) 數組
將Array2Tree(_JSON_)這個函數返回的數據處理成Json,'4'表明縮進4空白字符串,用於美化輸出(pretty-print)函數
二、arguments[0]學習
arguments對象是全部函數中可用的局部變量。你可使用arguments對象在函數中引用函數的參數。此對象包含傳遞給函數的每一個參數的條目,第一個條目的索引從0開始。這裏的arguments[0]實際就是取得咱們傳入函數的_JSON_數組。this
三、$.extend()spa
描述:將兩個或更多對象的內容合併到第一個對象。
也能夠是$.extend(boolean,dest,src1,src2,src3...)
第一個參數boolean表明是否進行深度拷貝不含第一個參數boolean,它的含義是將src1,src2,src3...合併到dest中,返回值爲合併後的dest,由此能夠看出該方法合併後,是修改了dest的結構的。因此這裏$.extend(true, [ ], arguments[0])的意思就是把傳的_JSON_數組合併到一個空的數組 [ ] 上去, 保證後續的操做不會改變arguments[0]的結構。
備註:$.extend(true, [ ], arguments[0]) , 也是能夠直接遍歷arguments[0]:
四、$.each()
jQuery的each方法是跟each的語義同樣是遍歷的做用。
當咱們第一參數是Array時:
$.each(Array, function(key, value){ this; // 這裏的this和value同樣都是指向每次遍歷Array中的當前元素 })
五、_This_ = TempMap[ this.id ] = This ? $.extend(this, _This_) : this;
這個裏面包含兩個知識點:
三目運算符: let variable = a ? b : c 即: a 能夠是任意能夠轉換成boolean類型的值或者運算,若是a爲true的話,上式等同於let variable = b; 不然 上式等同於let variable = c;
a = b = c : 等同於 b = c, a = b(注:只有 a 是能夠在這裏聲明變量的)。
六、邏輯或( a || b )運算的妙用
邏輯或運算( a || b ),其中a、b能夠是 boolean 類型或者任意能轉換成 boolean 類型的數據類型或者運算。在此段代碼中巧妙的運用到了變量的初始化上。a || b 運算的執行過程,只有當 a 爲 false 時 纔會執行 b, 只有 a 和 b 兩都是 false 會返回 false,不然返回a 或者 b,取決於 a 是不是true 或者是否能夠轉換爲true。
補充個基礎知識:在 js 的邏輯判斷中 null, 0, undefined, '', "" 均可以轉換爲 false。
在 Array2Tree 函數做用域內聲明一個 TempMap 的變量名,用於每項數據引用的臨時存儲
使用 $.each() 函數對 $.extend(true, [ ], arguments[0]) 獲得的新數組進行遍歷,$.each() 的第二個參數是一個匿名 function(){}, 咱們在 function(){} 裏對每一個數據進行處理,最終放置到變量 TempMap 中
在 function 的做用域中,this 指向每次遍歷中 Array 的當前元素。好比說第一次進入 function() 中的 this就是:{id :7, name: '豬', pid: 2}
var _This_ = TempMap[ this.id ]; // 尋找 TempMap 對象中 key 爲 this.id 的對應值。由於每個數據的id是惟一的,因此這裏的_This_獲得的值只有兩種可能: undefined 或者 { children:[object ...] }(這種狀況是由後面的代碼賦值而生成的)
_This_ = TempMap[ this.id ] = _This_ ? $.extend( this, _This_ ) : this;
// 若是在 TempMap 中沒有找到 key 爲 this.id 對應的值,也就是 This = undefined 的狀況,則把 this 直接賦值到 TempMap[ this.id ] 中去,而且讓 This 指向 this
// 若是找到了,就合併 This 到 this 對象上,而後再賦值給 TempMap[ this.id ],最後讓 This 指向 this。具體合併的效果能夠看下面的例子:
$.extend({id: 4, name: '雞', pid: 1}, {children: {id: 13, name: "三黃雞", pid: 4}}) 結果:{ id: 4, name: '雞', pid: 1, children: { id: 13, name: "三黃雞", pid: 4 } }
重要:這一步保證當前遍歷的元素以前的子元素能給 '穿' 到 TempMap[ this.id ] 上 ( ‘穿’ 理解成穿針引線通常的感受)。
this.pid = this.pid || 0;
// 獲取當前被遍歷的元素的 pid, 沒有 pid 的默認爲第一層,並賦予 this.pid = 0。這裏不必定非得是0,只要能和別的id區分開來就能夠,這裏採用0,是由於數據庫的索引通常從1開始計數。
var _Parent_ = TempMap[ this.pid ] = TempMap[ this.pid ] || { };
// 判斷 TempMap[ this.pid ] 是不是 undefined 。若是 TempMap[ this.pid ] 是 undefined,則 給TempMap[ this.pid ]賦值爲{},而且把 Parent 初始化爲 {}。不然 TempMap[ this.pid ] 不是 undefined時,則把 Parent 指向 TempMap[ this.pid ]。
( _Parent_.children = _Parent_.children || [ ] ).push( _This_ );
// 由於相比而言賦值運算的優先級相對別的要低一些,因此採起 ( Parent_.children = _Parent_.children || [ ] ) 方式保證 _Parent_.children 始終不是 undefined,而且是 array 類型。在這個條件下,咱們把 _This 存進_Parent_.children
重要:在這一步保證當前遍歷的元素能被 ‘穿’ 到對應的父元素上去。
return TempMap[ 0 ].children;
// 最終 TempMap 在本列中會變成以下形式:
![一個 key 爲 0, 1, ... 14, 15 的 Object][5]
而展開以後,咱們會發現想要的 ‘真正的樹’ 就是TempMap[ 0 ].children,效果見本文的第二張圖。那這又是什麼樣的結構呢?能夠這麼說 TempMap[ 0 ].children 是這棵樹結構的總體,而其他的1 至 15 是每一個對應的this.id 的分支。
補充一點:爲何在_Parent_.children賦值後,咱們的TempMap[ this.pid ]也隨之改變,這裏就涉及到引用數據的知識點了。在這裏由於_Parent_ = TempMap[ this.pid ],因此它們來指向同一個內存空間,在_Parent_改變後,內存空間中的值也就改變了,因此TempMap[ this.pid ]的值也就相應的改變了。也正是引用類型的數據的這個特色,保證了咱們的不管多少層的子元素都能被正確的 ‘穿’ 到了對應的父元素上
丈高樓始於平地,打好基礎知識異常重要!
文章出自 FCC(freeCodeCamp) 成都社區,歡迎你們的加入,和咱們一塊兒討論、學習~