专栏名称: freeCodeCamp
FreeCodeCamp.cn官方公众号
目录
相关文章推荐
数据中心运维管理  ·  中国互联网数据中心:AI核心资产被低估 ·  20 小时前  
梅森投研  ·  大风,又来了! ·  19 小时前  
人生资本论  ·  中央严厉表态,别多想了,“过紧日子”要求再加码 ·  19 小时前  
硅谷王川  ·  猎户座中间的连成一条线的三颗星 ... ·  3 天前  
阿尔法工场研究院  ·  专家访谈汇总:“大厂”竞赛,从抢用户变成了拼 ... ·  3 天前  
51好读  ›  专栏  ›  freeCodeCamp

"树" 的数据结构转化

freeCodeCamp  · 公众号  ·  · 2017-08-01 21:13

正文

一、问题描述


相信做前端的小伙伴都有遇到过将一个平铺的 ‘树’ 结构转换成一个真正的 ‘树’ 结构,比如说下面这种:


平铺的 ’树‘ 结构


最终要转换成类似如下的格式,方便在页面渲染:


类似这样的‘ 树 ’结构


你的方法是什么样的呢?思考中...


二、代码鉴赏


相信有的小伙伴会是和网上大多数能搜到的答案一样,用好几个循环来实现,在这里给大家解读一下,我认为看到代码最少的一种解决方案,该方案出自FCC成都社区的水歌之手,Jsbin代码地址: https://jsbin.com/budapagito/edit?html,js


十一行的代码实现将 ’平铺的树’ 转换为 ‘立体的树’ 结构


三、知识点分析


在看一段代码时,我们首先要了解里面涉及到的知识点(从方法入口开始):


1、JSON.stringify(Array2Tree(_JSON_), null, 4)


https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify


将Array2Tree(_JSON_)这个函数返回的数据处理成Json,'4'代表缩进4空白字符串,用于美化输出(pretty-print)


2、arguments[0]


https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments


arguments对象是所有函数中可用的局部变量。你可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数的条目,第一个条目的索引从0开始。这里的arguments[0]实际就是取得我们传入函数的_JSON_数组。


3、$.extend()


http://www.jquery123.com/jQuery.extend/


描述:将两个或更多对象的内容合并到第一个对象。

也可以是$.extend(boolean,dest,src1,src2,src3...)

第一个参数boolean代表是否进行深度拷贝不含第一个参数boolean,它的含义是将src1,src2,src3...合并到dest中,返回值为合并后的dest,由此可以看出该方法合并后,是修改了dest的结构的。所以这里$.extend(true, [ ], arguments[0])的意思就是把传的_JSON_数组合并到一个空的数组 [ ] 上去, 保证后续的操作不会改变arguments[0]的结构。


备注:$.extend(true, [ ], arguments[0]) , 也是可以直接遍历arguments[0]:


4、$.each()


http://www.jquery123.com/jQuery.each/


jQuery的each方法是跟each的语义一样是遍历的作用。


当我们第一参数是Array时:


$.each方法的解释


5、_This_ = TempMap[ this.id ] = _This_ ? $.extend(this, _This_)  :  this;


这个里面包含两个知识点:


三目运算符 : let variable = a ? b : c 即: a 可以是任意可以转换成boolean类型的值或者运算,如果a为true的话,上式等同于let variable = b; 否则 上式等同于let variable = c;

a = b = c : 等同于 b = c, a = b(注:只有 a 是可以在这里声明变量的)。


6、逻辑或( a || b )运算的妙用


逻辑或运算( a || b ),其中a、b可以是 boolean 类型或者任意能转换成 boolean 类型的数据类型或者运算。在此段代码中巧妙的运用到了变量的初始化上。a || b 运算的执行过程,只有当 a 为 false 时 才会执行 b, 只有 a 和 b 两都是 false 会返回 false,否则返回a 或者 b,取决于 a 是否是true 或者是否可以转换为true。


补充个基础知识:在 js 的逻辑判断中 null, 0, undefined, '', "" 都可以转换为 false。


四、思路分析


1、在 Array2Tree 函数作用域内声明一个 TempMap 的变量名,用于每项数据引用的临时存储


2、使用 $.each() 函数对 $.extend(true, [ ], arguments[0]) 得到的新数组进行遍历,$.each() 的第二个参数是一个匿名 function(){}, 我们在 function(){} 里对每个数据进行处理,最终放置到变量 TempMap 中


3、在 function 的作用域中,this 指向每次遍历中 Array 的当前元素。比如说第一次进入 function() 中的 this就是:{id :7, name: '猪', pid: 2}


var _This_ = TempMap[ this.id ];


// 寻找 TempMap 对象中 key 为 this.id 的对应值。因为每一个数据的id是唯一的,所以这里的_This_得到的值只有两种可能: undefined 或者 { children:[object ...] }(这种情况是由后面的代码赋值而生成的)


_This_  = TempMap[ this.id ] =  _This_ ? $.extend( this, _This_ ) : this;


// 如果在 TempMap 中没有找到 key 为 this.id 对应的值,也就是 _This_ =  undefined 的情况,则把 this 直接赋值到 TempMap[ this.id ] 中去,并且让 _This_ 指向 this

// 如果找到了,就合并 _This_  到 this 对象上,然后再赋值给 TempMap[ this.id ],最后让 _This_ 指向 this。具体合并的效果可以看下面的例子:


$.extend({id: 4,  name: '鸡',  pid: 1}, {id: 4,  name: '鸡',  pid: 1, children: {id: 13, name: "三黄鸡", pid: 4}})


上面例子的效果图


重要:这一步保证当前遍历的元素之前的子元素能给 '穿' 到 TempMap[ this.id ] 上 ( ‘穿’ 理解成穿针引线一般的感觉)。


this.pid = this.pid || 0;


// 获取当前被遍历的元素的 pid, 没有 pid 的默认为第一层,并赋予 this.pid = 0。这里不一定非得是0,只要能和别的id区分开来就可以,这里采用0,是因为数据库的索引一般从1开始计数。


var _Parent_ = TempMap[ this.pid ] = TempMap[ this.pid ] || { };


// 判断 TempMap[ this.pid ] 是否是 undefined 。如果 TempMap[ this.pid ] 是 undefined,则 给TempMap[ this.pid ]赋值为{},并且把 _Parent_ 初始化为 {}。否则 TempMap[ this.pid ] 不是 undefined时,则把 _Parent_  指向 TempMap[ this.pid ]。


( _Parent_.children = _Parent_.children || [ ] ).push( _This_ );


// 因为相比而言赋值运算的优先级相对别的要低一些,所以采取 ( _Parent_.children = _Parent_.children || [ ] ) 方式保证 _Parent_.children 始终不是 undefined,并且是 array 类型。在这个条件下,我们把 _This_ 存进_Parent_.children


重要:在这一步保证当前遍历的元素能被 ‘穿’ 到对应的父元素上去。


return TempMap[ 0 ].children;


// 最终 TempMap 在本列中会变成如下形式:


一个 key 为 0, 1, ... 14, 15 的 Object


而展开之后,我们会发现想要的 ‘真正的树’ 就是TempMap[ 0 ].children,效果见本文的第二张图。那这又是什么样的结构呢?可以这么说 TempMap[ 0 ].children 是这棵树结构的整体,而其余的1 至 15 是每个对应的this.id 的分支。


补充一点:为什么在_Parent_.children赋值后,我们的TempMap[ this.pid ]也随之改变,这里就涉及到引用数据的知识点了。在这里因为_Parent_ = TempMap[ this.pid ],所以它们来指向同一个内存空间,在_Parent_改变后,内存空间中的值也就改变了,所以TempMap[ this.pid ]的值也就相应的改变了。也正是引用类型的数据的这个特点,保证了我们的无论多少层的子元素都能被正确的 ‘穿’ 到了对应的父元素上


五、总结


万丈高楼始于平地,打好基础知识异常重要!

FCC 成都组织者-姜姜姜微信号







请到「今天看啥」查看全文