生活中的树和程序中的树
程序其实都是来源于生活,程序只是把他抽象化了,所以聊程序中的树之前,我想先找一下自然中的实例,在我们的周围到处都是树,树是大自然各种物体自我展现的一种最有效也是最简洁的方式,能很容易聚集很多零散的个体(独树成林就是这个道理),或者分析杂乱的事物和一些复杂的模型 比如下面这些就是很常见的例子:

而对于互联网呢?树也能清楚的展示层级结构,而不会显得凌乱,菜单的树如果命名得当很容易引导用户找到自己想要的内容,最最关键的是能节省空间,使得菜单简洁,展示更多网站主要内容。所以树已经在你不经意中融入了你的生命里了,融入你的代码和设计里了。
下面是我们程序中最常见的树:

其实抽象起来他们都有共同点:
都有隶属关系和层级关系。程序来源于生活,现实生活中的用户体验比互联网发展的时间长太多了,所以多留意生活中的细节,对理解互联网的交互体验很有帮助的。刚好最近写到树结构的相关模块,虽然以前写过树结构但是这一次我想多思考一些细节和为什么,总结一些规律和想法,以及最后实现的方式。
我会从后台数据库到前端展示整个打通来讲如何组织这颗“树”的。
首先来说说我的应用场景:
这个模块是一个故事会的管理的导航。

整个展现全都是用样式展现的没有用到任何图片,所以很适合洁癖群体使用。
首先得将一棵树进行抽象:
有两种节点,一种是叶子节点,一种是树杈节点。所以对应到数据库,会设计
pid isleaf 这两个字段,isleaf判断是不是叶子,pid(父id)来判断这个节点属于哪个节点的,根据这些就能完整的展现出一棵树,
比如有这样一组数据:
表: list

这些数据很清晰的能组成一颗树,如下。

叶子节点都是没有子节点的,他们在最下面(张无忌的故事,张三丰的故事,奋斗在深圳,都市言情),非叶子节点都有子节点,就是上图分叉的节点(故实会,武侠,都市,武当的故事)。
有坑请注意:开始建数据的时候我有考虑过去掉故事会这个节点,只要是pid为0的都是故实会,这种方式很多人在用(比如我的上家公司就这样干的),而且没什么问题,用着也很顺手。但是如果联想想到我们现实世界的实例,能找到没有树干的树吗?这么一想问题就迎刃而解了,故实会就是我们的树干,他也有非叶子节点的属性,所以故事会这个节点还是得加在数据中,其实问题不止这些,如果哪天不叫故事会了叫故事集,程序里面你得改很多地方,程序逻辑是不应该参杂数据在里面的,做到不依赖数据,这也是设计时需要考虑的基本原则。
好了后端的(数据)树出来了,下来就来思考我们前端展现的树。
上面画的图是我根据数据库里的数据然后通过思考画出来的树结构图,我们需要把这个大脑思考的过程用程序的方式模拟出来。但是我们大脑的思维是自上而下的,我们都是会先找到树干然后一层层的加树枝,而程序里面我们得从下而上,因为所有的节点理论上说都有一个父节点,但是如果自上而下用子节点标记(如cid),叶子节点都没有子节点,而且一个节点的子节点个数未知,存储还得以逗号隔开如:(id1,id2),判断起来情况比较多而且复杂。
所以我们得换种思路考虑,再回过头来看看程序中的树:

所有三角形的部分都是节点,我们只需要知道每个节点下面都是什么不就完了?
是的,我们只需要知道每个非叶子节点下面的子节点就行了,这里有两种方式,一种实时请求数据的,就是点击节点得到id再跑去数据库查询pid = id 的所有节点,我觉得实在没太大必要,除非数据量太多,而且对效率,速度都要求不高的时候可以这么干,还有一种树,也是我觉得效率更高的一种树,就是一次请求下来所有的数据组成树,这样点击响应速度更快,而且服务器开销也小一些,做为前端第一个考虑的最重要的体验就是速度,中国的网速你懂的,用户如果点一下等几秒,估计会疯掉。
比如我上天猫发现是这样的,我网速慢可能是一个原因,但是这种异步请求的方式我实在是觉得不好,因为左边菜单鼠标滑过的时候就会展现右侧数据,如果不是异步请求直接展现出来了,我心理上会觉得这个网站速度很快,而且这么重要的数据,尽然不是第一次请求,我很难理解。
我很好奇登录不尽快展现登陆框,而是优先展现一张图片,我有几次图片出来了,但登陆框出现加载失败。
因为是iframe对网页性能消耗较大,会经常出现iframe加载失败。那叫一个哭啊。(不知道具体为什么这么做,如果有人了解欢迎解惑!)
呀!跑题了,我们继续说树,光拿到数据还是不够的,首先我们需要对这些数据整理一下(整理成非叶子节点的集合,可以这么干):
js代码如下:
var list = {};
var cids = '';
for(var i = 0, len = data.length; i < len; i++){
if(!list[data[i][ 'pid' ]]){
list[data[i][ 'pid' ]] = [ data[i] ];
}else{
list[data[i][ 'pid' ]].push(data[i]);
}
}
console.dir(list);
整理之后显示结果如下:

(其实就是整理成了我们大脑的思维方式,自上而下的找子节点)。
点击就展现出下面的节点如:

每一个节点会有一个click事件,点击找到上面id对应的数组,循环拼接dom,然后做展现。
树虽然展现成功了,但是紧接着我又遇到了问题,比如我点击节点“武侠”,我需要看到武侠下面的所有文章,其实这种情况也很普遍,也有必要的。如果我的数据是这样的:

根据这种需求,我第一个想到sql语句可以这么写 SELECT name FROM article WHERE list_id in 'id1,id2,id3,id4';
我只要找到这些id就行了,再一思考我只要找到所有节点下的叶子节点就行了,一个递归就能搞定了,于是又有了下面的这段代码。
_getCid : function(id){
if(id < 0) return;
var list = this.list[id];
if(!list) return;
for(var i = 0, len = list.length; i < len; i++){
if(list[i]['isleaf'] == 1){
this.cids += list[i][ 'id' ] + ',';
}else{
arguments.callee.call(this, list[i][ 'id' ]);
}
}
}
this.cids 是我们的组件对象,可能这里看不明白,下面我会给出组件的源码,下下来整个看就明白了。
最后完成的实例是这样的: 疯狂的戳点这里查看树>>
这样终于算是完成了一棵树的完整展现了。其实我遇到的问题还远不止这些。
这是ajax的一个树结构展现,不用刷新页面,所以js可以用变量来存储列表数据。
但是还有一种需求无法避免,就是后台也会需要这种展现,而且每次点击会刷新页面(比如多级导航),所以我们得保存列表数据在服务器内存里,但是怎么保存呢?如果每次都读取数据库显然不太好,毕竟可能数据都是一样的,所有人都重复请求同样的数据,这事作为一个程序员的我来说是不能忍受的。所以我用了服务器缓存技术,来存贮列表,当列表数据变动的时候我才去更新缓存。
下面是完整demo的下载地址:
下载地址>>
下载之前看看这是不是你想要的>>
没有用图片用样式和字符模拟的树结构,速度很快哦!
原创文章,可随意转载,但是请注明出处,感谢支持!
文章地址:生活中的树和程序中的树

