(网页)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!
语罢,又是一道题飞来!
哦,还带了儿子来当帮手。我一个循环再一个循环,轻松带走你们
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如何用递归写一个简单的树形结构的更多相关文章
- javascript如何用递归写一个简单的树形结构
现在有一个数据,需要你渲染出对应的列表出来: var data = [ {"id":1}, {"id":2}, {"id":3}, {&qu ...
- Mark: 如何用Haskell写一个简单的编译器
作者:aaaron7 链接:https://www.zhihu.com/question/36756224/answer/88530013 如果是用 Haskell 的话,三篇文章足矣. prereq ...
- (原创)如何使用boost.asio写一个简单的通信程序(二)
先说下上一篇文章中提到的保持io_service::run不退出的简单办法.因为只要异步事件队列中有事件,io_service::run就会一直阻塞不退出,所以只要保证异步事件队列中一直有事件就行了, ...
- 分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”
这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业 ...
- 如何用PHP/MySQL为 iOS App 写一个简单的web服务器(译) PART1
原文:http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app 作为一个i ...
- 如何写一个简单的webserver(一):最简实现
本文主要讲述如何用C/C++在Linux环境下写一个简单的支持并发的web服务器,并不考虑服务器的健壮性.安全性.性能等一系列因素. 在本文中,该服务器仅支持GET请求. 项目地址:https://g ...
- 用node.js从零开始去写一个简单的爬虫
如果你不会Python语言,正好又是一个node.js小白,看完这篇文章之后,一定会觉得受益匪浅,感受到自己又新get到了一门技能,如何用node.js从零开始去写一个简单的爬虫,十分钟时间就能搞定, ...
- linux常用终端指令+如何用vim写一个c程序并运行
在装好ubuntu之后今天学习了一些linux的一些基础知识: windows里面打开命令窗口是win+r,在linux系统里面,ctrl+alt+t打开终端,今天的一些指令都是围绕终端来说的 首先s ...
- 写一个简单的 Linux Shell (C++)
这里可以找到代码 github.com/z0gSh1u/expshell 支持的特性 单条指令的执行 引号引起的参数(如 $ some_program "hello, world" ...
随机推荐
- Swift 里的指针
 基础知识 指针的内存状态 typed? initiated? ❌ ❌ ✅ ❌ ✅ ✅ 之前分配的内存可能被释放,使得指针指向了未被分配的内存. 有两种方式可以使得指针指向的内存处于Uninitia ...
- Taglist: Exuberant ctags (http://ctags.sf.net) not found in PATH. Plugin is not loaded
1 开发环境 Ubuntu16.04(64bit) 2 错误描述 安装好Vim的TagList插件后,打开Vim提示: 3 解决方法 根据参考资料[1]的提示,可知那是因为当前系统没有安装ctags导 ...
- Tools - 源代码阅读分析工具Source Insight
简介 https://www.sourceinsight.com/ Source Insight是一个面向项目开发的程序编辑器和代码浏览器,可以分析C/C++.C#.Java.Python等语言源代码 ...
- Http请求-get和post的区别
GET和POST是HTTP请求的两种基本方法. 最直观的区别就是GET把参数包含在URL中,以?的方式来进行拼接,POST通过request body传递参数.并且GET请求在URL中传送的参数是有长 ...
- 脚手架vue-cli系列五:基于Nightwatch的端到端测试环境
不同公司和组织之间的测试效率迥异.在这个富交互和响应式处理随处可见的时代,很多组织都使用敏捷的方式来开发应用,因此测试自动化也成为软件项目的必备部分.测试自动化意味着使用软件工具来反复运行项目中的测试 ...
- hdu 6127---Hard challenge(思维)
题目链接 Problem Description There are n points on the plane, and the ith points has a value vali, and i ...
- .net core在Linux下获取AD域信息
.net core在Linux下获取AD域信息 .net Core 2.1.4 .net core现在System.DirectoryServices只支持Windows平台下使用. 参考: http ...
- 给recycleview加headview
参考了https://blog.csdn.net/qibin0506/article/details/49716795 由于recycleview没有直接添加头部view的api,所以需要我们自己去添 ...
- 音频标签化2:youtube-8m项目的训练、评估与测试
之前小程介绍了使用机器学习的办法来解决"音频标签化"的问题,并且提到了训练样本audioset跟youtube-8m的dataset,而训练模型上也提到了youtube-8m的模型 ...
- MongoDB 系列文章
MongoDB 系列文章 本文的内容是基于 MongoDB 4.0 的. 参考于 MongoDB 4.0 官方文档. 搭建 MongoDB从搭建到优化 MongoDB-副本集搭建与管理 管理 Mong ...