javascript如何用遞歸寫一個簡單的樹形結構

如今有一個數據,須要你渲染出對應的列表出來:javascript

var data = [
  {"id":1},
  {"id":2},
  {"id":3},
  {"id":4}, 
];

var str="<ul>";

data.forEach(function(v,i){
   str+="<li><span>"+v.id+"</span></li>"
})

str="</ul>"

$(doucment).append(str);

哼,easy!java

語罷,又是一道題飛來!node

哦,還帶了兒子來當幫手。我一個循環再一個循環,輕鬆帶走大家json

var data2 = [
    {"id":1,children:[{"id":"child11"},{"id":"child12"}]},
    {"id":2},
    {"id":3children:[{"id":"child31"},{"id":"child32"}]},
    {"id":4}, 
];


var str="<ul>";

data2.forEach(function(v,i){
    if(v.children&&v.children.length>0){
        str+="<li><span>"+v.id+"</span>";
        str+="<ul>";
        v.children.forEach(function(value,index){
             str+="<li><span>"+value.id+"</span>";
        })
        str="</ul>";
        str="</li>";

    }else{
        str+="<li><span>"+v.id+"</span></li>"
    }  
})
str="</ul>"

$(doucment).append(str);

還有誰?數組

var json=[
        {
          name:123,id:1
          children:[
            {
              name:453,id:456,children:[{name:789,id:777,children:[{name:"hahahqqq---qq",id:3232,children:[name:'son',id:"13132123211"]}]}]
            },
            {
              name:"Cessihshis" , id:12121
            }
          ]
        },
        {
          name:"啊啊啊11", id:12
        },
      ];

居然把全家都帶來了,看我循環循環再循環大法。app

嗯,不知道他家幾代同堂,我該循環幾回?忽然間你感受遇到對手了。函數

正納悶着,忽然有人拍了一下你的肩膀,兄弟,我這裏有一本遞歸祕籍,我看你骨骼驚奇,是個練武奇才,10塊錢賣你了。測試

function render(treeJson){   
    if(!Array.isArray(treeJson)||treeJson.length<=0){return ""}   
    var ul=$("<ul>");
    treeJson.forEach(function(item,i){      
      var li=$("<li><span class='treeName'>"+item.name+"</span></li>");
      if(Array.isArray(item.children)&&item.children.length>0){
        li.append(render(item.children))
      }
      ul.append(li);
    })
    return ul
  }

  $(document).append(render(json));

好了不扯了,經過遞歸,無需再判斷數據有多少層級,只有當前數組有children而且長度大於0,函數就會遞歸調用自身,而且返回一個ul。spa

這樣一來,一顆很是簡陋的樹就生成了,不過一般樹都帶有radio或者checkbox選擇框,並且不少時候都須要對樹的右側進行拓展,好比加一些新增,編輯等按鈕什麼的,能夠考慮多傳一個對象做爲參數。prototype

var checkbox={
        radio:"<label class='myTreeIcon'><input type='radio'  name='selectTreeRedio'><span></span></label>",

        multi:"<input type='checkbox' name='selectTreeRedio'>"
      }


      function render(treeJson,option={type:0,expandDom:function(){}}){   
         if(!Array.isArray(treeJson)||treeJson.length<=0){return ""}   
          var {type,expandDom}=option;        
          var ul=$("<ul>");
          treeJson.forEach(function(item,i){
            var str="";
            if(type==1){
              str+=checkbox.multi
            }else if(type==2){
              str+=checkbox.radio
            }
            var li=$("<li data-id='"+item+"'>"+str+"<span class='treeName'>"+item.name+"</span></li>");
            expandDom&&expandDom(li,item);
            if(item.children&&item.children.length>0){
              li.append(render(item.children,option))
            }
            ul.append(li);
        })
        return ul
      }

      //option使用了一個默認對象,默認爲不須要選擇框和不須要拓展, 若是傳入的type爲1或者2,則生成checkbox或者radio,因爲radio樣式比較醜,用label包起來本身模擬選中的效果;若是傳入拓展參數,則把當前的父級li以及當前的參數傳入,以便進行拓展。


      $("#tree").append(render(json,{
        type:1,
        expandDom:function(el,data){
          el.append("<button>編輯</button><button>測試</button><a data-msg='"+JSON.stringify(data)+"'></a>")
        }
      }))

有時候後臺返回的可能不是拼裝好層級的數組,而是帶有pid標識的全部數組的集合,好比:

var data = [
  {"id":2,"name":"第一級1","pid":0},
  {"id":3,"name":"第二級1","pid":2},
  {"id":5,"name":"第三級1","pid":4},
  {"id":100,"name":"第三級2","pid":3},
  {"id":6,"name":"第三級2","pid":3},
  {"id":601,"name":"第三級2","pid":6},
  {"id":602,"name":"第三級2","pid":6},
  {"id":603,"name":"第三級2","pid":6}
];

爲了用遞歸來渲染出樹來,這時,就須要咱們手動來將層級裝好了:

function arrayToJson(treeArray){
    var r = [];
    var tmpMap ={};

    for (var i=0, l=treeArray.length; i<l; i++) {
     // 以每條數據的id做爲obj的key值,數據做爲value值存入到一個臨時對象裏面
      tmpMap[treeArray[i]["id"]]= treeArray[i]; 
    } 

    for (i=0, l=treeArray.length; i<l; i++) {
      var key=tmpMap[treeArray[i]["pid"]];
      
      //循環每一條數據的pid,假如這個臨時對象有這個key值,就表明這個key對應的數據有children,須要Push進去
      if (key) {
        if (!key["children"]){
            key["children"] = [];
            key["children"].push(treeArray[i]);
        }else{
          key["children"].push(treeArray[i]);
        }       
      } else {
        //若是沒有這個Key值,那就表明沒有父級,直接放在最外層
        r.push(treeArray[i]);
      }
    }
    return r
   }

如今咱們已經實現了將沒有層級結構的數組轉化爲帶有層級的數組,那麼問題來了,樹形圖還經常會須要帶搜索功能,有沒有辦法把帶層級結構的數組轉化爲不帶層級結構的一個數組呢?由於若是不帶層級的話,進行搜索等操做時就很是方便,一個filter基本就能夠搞定了。

var jsonToArray=function (nodes) {
      var r=[];
      if (Array.isArray(nodes)) {
        for (var i=0, l=nodes.length; i<l; i++) {
          r.push(nodes[i]);
          if (Array.isArray(nodes[i]["children"])&&nodes[i]["children"].length>0)
           //將children遞歸的push到最外層的數組r裏面
            r = r.concat(jsonToArray(nodes[i]["children"]));
              delete nodes[i]["children"]
        }
      } 
      return r;
    }

這樣,無論後臺返回什麼格式給咱們,咱們均可以自由的互轉了,不論是帶層級的轉不帶層級的,仍是把不帶層級的轉化爲帶有層級的,都只須要調用一個函數就能夠輕鬆解決。

不過這裏有一個隱患,就是因爲對象的引用關係,操做後雖然返回了咱們須要東西,可是會改變原來的數據。

爲了避免影響到原來的數據,咱們須要複製一份數據,須要進行一次深拷貝。

爲何是深拷貝而不是淺拷貝?由於淺拷貝只會複製最外面的一層,假入某一個key值裏面又是一個對象,那對複製後的對象的這個key的值進行操做通用會影響到原來的對象。淺拷貝的方法有不少,ES6的assign,jq第一個參數不爲true的 $.extend(),數組的slice(0),還有不少不少。

對於標準的json格式的對象,能夠用JSON.parse(JSON.stringify(obj))來實現。固然,本文寫的是遞歸,因此仍是來手寫一個

function deepCopy(obj){
    var object;
    if(Object.prototype.toString.call(obj)=="[object Array]"){    
      object=[];
      for(var i=0;i<obj.length;i++){
        object.push(deepCopy(obj[i]))
      }   
      return object
    }

    if(Object.prototype.toString.call(obj)=="[object Object]"){   
      object={};
      for(var p in obj){
        object[p]=obj[p]
      }   
      return object
    }
  }

其實有點相似於淺拷貝,淺拷貝會複製一層,那麼咱們判斷某個值是對象,經過遞歸再來一次(比如飲料中獎再來一瓶同樣,若是中獎了,就遞歸再來一瓶,又中獎就又遞歸再來一瓶,直到再也不中獎),也就是說咱們經過無盡的淺拷貝來達到複製一個徹底的新的對象的效果。

這樣,對樹結構操做時,只須要傳入深拷貝後新對象,就不會影響原來的對象了;

jsonToArray(deepCopy(data));

亦或是

arrayToJson(deepCopy(data)):
相關文章
相關標籤/搜索