现在有一个数据,需要你渲染出对应的列表出来:

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!

语罢,又是一道题飞来!

哦,还带了儿子来当帮手。我一个循环再一个循环,轻松带走你们

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
},
];

竟然把全家都带来了,看我循环循环再循环大法。

嗯,不知道他家几代同堂,我该循环几次?突然间你感觉遇到对手了。

正纳闷着,突然有人拍了一下你的肩膀,兄弟,我这里有一本递归秘籍,我看你骨骼惊奇,是个练武奇才,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。

这样一来,一颗非常简陋的树就生成了,不过通常树都带有radio或者checkbox选择框,而且很多时候都需要对树的右侧进行拓展,比如加一些新增,编辑等按钮什么的,可以考虑多传一个对象作为参数。

      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)):

javascript如何用递归写一个简单的树形结构的更多相关文章

  1. (网页)javascript如何用递归写一个简单的树形结构

    转自博客园: 现在有一个数据,需要你渲染出对应的列表出来: var data = [ {"id":1}, {"id":2}, {"id":3 ...

  2. Mark: 如何用Haskell写一个简单的编译器

    作者:aaaron7 链接:https://www.zhihu.com/question/36756224/answer/88530013 如果是用 Haskell 的话,三篇文章足矣. prereq ...

  3. (原创)如何使用boost.asio写一个简单的通信程序(二)

    先说下上一篇文章中提到的保持io_service::run不退出的简单办法.因为只要异步事件队列中有事件,io_service::run就会一直阻塞不退出,所以只要保证异步事件队列中一直有事件就行了, ...

  4. 如何用PHP/MySQL为 iOS App 写一个简单的web服务器(译) PART1

    原文:http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app 作为一个i ...

  5. 分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”

    这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业 ...

  6. 如何写一个简单的webserver(一):最简实现

    本文主要讲述如何用C/C++在Linux环境下写一个简单的支持并发的web服务器,并不考虑服务器的健壮性.安全性.性能等一系列因素. 在本文中,该服务器仅支持GET请求. 项目地址:https://g ...

  7. 用node.js从零开始去写一个简单的爬虫

    如果你不会Python语言,正好又是一个node.js小白,看完这篇文章之后,一定会觉得受益匪浅,感受到自己又新get到了一门技能,如何用node.js从零开始去写一个简单的爬虫,十分钟时间就能搞定, ...

  8. linux常用终端指令+如何用vim写一个c程序并运行

    在装好ubuntu之后今天学习了一些linux的一些基础知识: windows里面打开命令窗口是win+r,在linux系统里面,ctrl+alt+t打开终端,今天的一些指令都是围绕终端来说的 首先s ...

  9. 写一个简单的 Linux Shell (C++)

    这里可以找到代码 github.com/z0gSh1u/expshell 支持的特性 单条指令的执行 引号引起的参数(如 $ some_program "hello, world" ...

随机推荐

  1. (转)volatile关键字

    Java线程:volatile关键字   Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量 ...

  2. [js高手之路] es6系列教程 - 对象功能扩展详解

    第一:字面量对象的方法,支持缩写形式 //es6之前,这么写 var User = { name : 'ghostwu', showName : function(){ return this.nam ...

  3. POJ2236 Wireless Network 并查集简单应用

    Description An earthquake takes place in Southeast Asia. The ACM (Asia Cooperated Medical team) have ...

  4. 谈一谈EasyUI的TreeGrid的过滤功能

    写在最前面 这个星期一直在纠结easyui的treegrid的过滤功能,原因呢,自然是项目中一个莫名奇妙的需求. easyui虽说是后端程序员的前端框架,但是说句实话,除去api,让我直接写里面的节点 ...

  5. js验证15位或18位身份证

    本篇文章是本人在网上搜集了一些验证,然后又个人进行一定修改的关于身份证的验证,欢迎修改指正..... function IdCardValidateRule(idCard) { var tip;    ...

  6. 在myeclipse的jsp编辑器中怎么把Source/Preview调出来的方法步骤

    1.点击要打开的jsp 文件. 右键.选择 open with -->  MyEclipse Visual JSP Designer 就好了. 2. 如果想所有的jsp  都默认使用 这个可视化 ...

  7. Appium总结

    运行case前,最好在case前加入等待,以免activity还未关闭,就开始定位或其他操作(如果case总报错,则考虑此问题): swipe效果: Thread.sleep(2000);       ...

  8. macvlan 网络结构分析 - 每天5分钟玩转 Docker 容器技术(56)

    上一节我们创建了 macvlan 并部署了容器,本节详细分析 macvlan 底层网络结构. macvlan 网络结构分析 macvlan 不依赖 Linux bridge,brctl show 可以 ...

  9. MySQL索引篇

    innodb索引概念   总结记录下innodb的索引概念,以备查看   innodb索引分类: 聚簇索引(clustered index)     1)  有主键时,根据主键创建聚簇索引     2 ...

  10. c# 函数简述

    函数是具有独立功能,并且能够重复使用的代码块.函数必须先声明后调用,使用函数使代码更简洁易读. 一.函数的声明与调用 1.声明格式: static 返回类型 函数名(参数类型 参数名称,参数类型 参数 ...