之前在做遍历二叉树结构的的DOM时,只是根据百度ife的参考资料(就是下面的学员笔记)完成了任务,并没有实际理解递归的原理,现在在做to-do-list时又遇到了类似的问题,所以看了一些文章,大概了解了递归的原理,在这里整理一下。
在查找相关文章时,看到一个比较重要的概念,就是js的执行上下文,而以前在前端早读课上恰好看到过一篇相关文章,现在把内容大概整理一下。
原文链接:http://mp.weixin.qq.com/s/TAZax-B-llfVXgZyKYJ_VQ(前端早读课微信公众号文章)
文章里对于执行上下文(Execution [ˌeksɪˈkju:ʃn] Context)已经解释的很清楚了,那么我再写几点我自己的理解:
- 每一次调用自身时都会开辟一块新的内存用来保存之前函数中的值并压入栈中,所以说这里你可以看成是另一个新的函数在运行,新函数中的各种值、变量等等都跟调用之前的函数没什么关系,虽然它调用的就是它自己。(当然了,如果上一个函数需要新函数的return值,那还是有点关系的)
- 递归调用的那一刻,个人觉得有点像是中断一样,就是碰到一个递归调用了,我就把眼前的函数运行先停下来,先去执行新的函数,等新的函数执行完了,控制权又回到了我的手里,我再继续干我没干完的工作,不知道这算不算是异步啊~~(还是小白一只,对许多概念也还摸不清,只能浅谈一下自己的理解,若有大佬,请不吝赐教)
那么这里就那百度ife2017里面的那个遍历dom的基础题(这里只是前序遍历)来做一个例子:
HTML代码:
<div class="node1">1 <div class="node2-1">2 <div class="node3-1">3</div> <div class ="node3-2">3</div> </div> <div class="node2-2">2 <div class="node3-1">3</div> <div class="node3-2">3</div> </div> </div>
js代码:
function preOrder(node){ if(node){ arr_dom.push(node); preOrder(node.firstElementChild); preOrder(node.lastElementChild); } }
其中arr_dom一开始是一个空数组。
那么它就会一直向下执行,参数变化依次为1 --> 2-1 --> 3-1 并且在每次调用时创建一个新的执行上下文,这里关键就是左子树运行到最后一个结点,也就是当函数的参数class是3-1这个dom节点(下文中简称为3-1,其它dom节点也如此)时所发生的事情,3-1的左孩子即firstElementChild为null(没怎么学过数据结构,不知道是不是这么叫,上网查了一下应该是这样吧,大家明白就好),那么此时它会以null为参数继续执行递归,那么显然它无法通过if语句,此时这个函数就已经执行完毕了,这个执行上下文就会从栈顶弹出,参数恢复到3-1。(这里其实是指的执行上下文的变化)
这里是我当时理解比较困惑的地方,参数恢复到3-1,的话,不是又要执行刚才的过程了吗(firstElementChild为null,然后……),后来发现并不是这样,也就是上面自己的理解中的第二点,参数node恢复到3-1时并不会又从头开始执行函数,而是从上次中断的地方继续执行,也就是它会执行preOrder(node.lastElementChild)这条语句,然后和刚才一样的过程,参数会从null变为3-1,而3-1此时已经执行完了第二条语句,所以这个执行上下文也会从栈顶弹出,那么此时node这个参数又会变为2-1,然后2-1再执行第二条语句……
这样以此类推就能遍历整个dom树了,当然这只是二叉树的遍历,如果不是二叉树,可采用一个for循环,像下面这样
function preOrder(node){ var childs = node.children,item; //对node的处理 if(node){ arr_dom.push(node); } for(var i=0; i < childs.length ; i++){ item = childs[i]; if(item.nodeType === 1){ //递归先序遍历子节点 preOrder(item); } } }
其中nodeType === 1表示它是一个元素节点
但是递归调用会不断占用内存,效率不高,不过我暂时也不知道别的遍历方法,如果自己研究了别的办法,就再来总结一下。
如果有大神知道别的方法或者上述内容哪里有错误的,希望能在评论里不吝赐教下,谢谢