之前在做遍歷二叉樹結構的的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表示它是一個元素節點
但是遞歸調用會不斷占用內存,效率不高,不過我暫時也不知道別的遍歷方法,如果自己研究了別的辦法,就再來總結一下。
如果有大神知道別的方法或者上述內容哪里有錯誤的,希望能在評論里不吝賜教下,謝謝
