代碼解析 | '樹'的數據結構轉化

1、問題描述

相信作前端的小夥伴都有遇到過將一個平鋪的 ‘樹’ 結構轉換成一個真正的 ‘樹’ 結構,好比說下面這種: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


2、代碼鑑賞

相信有的小夥伴會是和網上大多數能搜到的答案同樣,用好幾個循環來實現,在這裏給你們解讀一下,我認爲看到代碼最少的一種解決方案,該方案出自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
    ));

3、知識點分析

在看一段代碼時,咱們首先要了解裏面涉及到的知識點(從方法入口開始):數據庫

一、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。

4、思路分析

  1. 在 Array2Tree 函數做用域內聲明一個 TempMap 的變量名,用於每項數據引用的臨時存儲

  2. 使用 $.each() 函數對 $.extend(true, [ ], arguments[0]) 獲得的新數組進行遍歷,$.each() 的第二個參數是一個匿名 function(){}, 咱們在 function(){} 裏對每一個數據進行處理,最終放置到變量 TempMap 中

  3. 在 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 ]的值也就相應的改變了。也正是引用類型的數據的這個特色,保證了咱們的不管多少層的子元素都能被正確的 ‘穿’ 到了對應的父元素上

5、總結

丈高樓始於平地,打好基礎知識異常重要!


文章出自 FCC(freeCodeCamp) 成都社區,歡迎你們的加入,和咱們一塊兒討論、學習~
me

相關文章
相關標籤/搜索