前言
关于树的问题已经是一个老生常谈的话题了,但是大部分情况下都是通过后台进行异步获取数据,如果使用javascript怎么实现数据库中的线性数据向树形数据转换呢?今天我们就一起来整理一下。
必备小知识
js中有六大数据类型,包括五种基本数据类型(Number,String,Boolean,Undefined,Null),和一种复杂的引用数据类型(Object),为了更好的理解引用数据类型的不同之处我们通过下面这个例子看看。
题目1: var a = 100;
var b = a;
a = 200;
console.log (b);
题目2: var a = {age : 20};
var b = a;
b.age = 21;
console.log (a.age);
题目1的答案是 100,题目2的答案是21,
题目1
简单的值类型,在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,然后再把该值复制到为新变量分配的位置上。此时,a中保存的值为 100 ,当使用 a 来初始化 b 时,b 中保存的值也为100,但b中的100与a中的是完全独立的,该值只是a中的值的一个副本,此后,这两个变量可以参加任何操作而相互不受影响。也就是说基本类型在赋值操作后,两个变量是相互不受影响的。
题目2
引用类型,当从一个变量向另一个变量赋值引用类型的值时,同样也会将存储在变量中的对象的值复制一份放到为新变量分配的空间中。
这时保存在变量中的是对象在堆内存中的地址,所以,与简单赋值不同,这个值的副本实际上是一个指针,而这个指针指向存储在堆内存的一个对象。那么赋值操作后,两个变量都保存了同一个对象地址,则这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响。
因此,引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象,任何的操作都会相互影响。
补充
Object包括Object Array Function,他们都是对象,Number Boolean String也可以通过new Number()、new Number()、new String()创建但是他们和对象是有差异的,官方文档中将这种称为包装对象,具体解释可以看javascript权威指南的第三章3.6包装对象一节,在这里主要区分引用数据类型和简单值类型的区别。
基本原料
下面这段代码可以理解为通过在数据库中查询获得的数据,该数据为数组结构,数组中每个元素都是对象,切记对象是引用数据类型;
var list = [{
id: "a",
pid: null,
name: "理科"
}, {
id: "b",
pid: "a",
name: "数学"
}, {
id: "c",
pid: "a",
name: "物理"
}, {
id: "d",
pid: "a",
name: "化学"
}, {
id: "e",
pid: "f",
name: "动物"
}, {
id: "f",
pid: "a",
name: "生物"
}, {
id: "g",
pid: "h",
name: "政治"
}, {
id: "j",
pid: "h",
name: "历史"
}, {
id: "h",
pid: null,
name: "文科"
}, {
id: "i",
pid: "j",
name: "近代史"
}, {
id: "k",
pid: "i",
name: "鸦片战争"
}];
线形数据转换为树形数据
有了基础的数据后,我们通过下面的方法对数据进行处理,代码中关键部分都已经做了备注,主要疑问可能是map怎么就把值给了list,再友情提醒下对象是引用传递~
具体代码
function parseList(list){
//创建一个对象命名为map
var map={};
//通过遍历把list中的元素放到map对象中
list.forEach(function(item){
if(!map[item.id]){
//核心步骤1:map中的'item.id'属性指向list数组中的对象元素
map[item.id]=item;
}
});
//再次遍历为了对map属性所指的对象进行处理
list.forEach(function(item){
//过滤父级id不是null的元素
if(item.pid!=null){
//map[item.pid]为该元素的父级元素
map[item.pid].children ? map[item.pid].children.push(item):map[item.pid].children=[item];
}
});
//过滤后仅剩下根节点
return list.filter(function(item){
if(item.pid===null){
return item;
}
});
}
var newList=parseList(list);
代码中的注释已经写的很详细了,我们可以得知每次操作看似对map进行处理,实际上是处理了list和map共同指向的对象,因此在给父级添加属性的过程,其实也同时给list的对象添加属性,因此我们通过var newList=parseList(list),可以得到下面的运行结果
运行结果
通过递归调用构建树形结构
这一步就简单很多了,一个简单的递归调用就可以解决对树形结构的处理,具体代码如下所示:
function render(treeJson){
if(Array.isArray(treeJson)&&treeJson.length>0){
//创建一个ul对叶子节点进行包装
var ul="<ul>\n";
//遍历生成叶子节点li
treeJson.forEach(function(item){
var li="<li>"+item.name+"</li>\n";
//如果叶子节点还有子节点,通过递归调用生成
if(Array.isArray(item.children)&&item.children.length>0){
li+=render(item.children);
}
ul+=li;
});
return ul+"</ul>\n";
}else{
return "";
}
}
var render=render(newList);
运行结果
我们通过var render=render(newList)获得字符串结果如下所示
<ul>
<li>理科</li>
<ul>
<li>数学</li>
<li>物理</li>
<li>化学</li>
<li>生物</li>
<ul>
<li>动物</li>
</ul>
</ul>
<li>文科</li>
<ul>
<li>政治</li>
<li>历史</li>
<ul>
<li>近代史</li>
<ul>
<li>鸦片战争</li>
</ul>
</ul>
</ul>
</ul>
效果展示
因为这里使用的是不引用jquery的原始方法,因此我们通过字符串拼接模拟生成ul与li的嵌套关系,如果想看效果的话,可以把生成字符串复制到w3c之类的网页中测试就可以看到效果http://www.w3school.com.cn/tiy/t.asp?f=html_basic
后续会补充ztree的使用大家有兴趣的朋友可以关注我的更新