背景
異步樹展開如果要實作展開回呼比較困難,因為展開的程序是異步的,
前端:js引擎雖然是單執行緒執行,但是操作ui的執行緒是單獨的,樹的展開程序,就經歷了js引擎執行緒+ui執行緒的程序,展開代碼和展開回呼的代碼在不同時機執行的,本質上就是異步的,
展開回呼的實作
展開回呼的實作困難點在于判斷展開結束的時機,樹的展開是一個遞回程序,本質上就是判斷遞回的結束時機,遞回的程序,其實就是進堆疊出堆疊的程序,如果我們能夠統計進堆疊出堆疊的次數,就能夠判斷出結束的時機,下面是驗證代碼(Java)
1.樹
package com.hdwang.test.recursize; import java.util.ArrayList; import java.util.List; /** * @author wanghuidong * 時間: 2022/8/13 22:36 */ public class TreeNode { private int id; private int level; List<TreeNode> children = new ArrayList<>(); public int getId() { return id; } public void setId(int id) { this.id = id; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public List<TreeNode> getChildren() { return children; } public void setChildren(List<TreeNode> children) { this.children = children; } @Override public String toString() { return "TreeNode{" + "id=" + id + ", level=" + level + '}'; } }
2.回呼介面
package com.hdwang.test.recursize; /** * @author wanghuidong * 時間: 2022/8/13 22:54 */ @FunctionalInterface public interface Callback { void callback(); }
3.樹的展開回呼實作
package com.hdwang.test.recursize; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; /** * 遞回測驗 * * @author wanghuidong * 時間: 2022/8/13 22:34 */ public class RecursiveTest { static AtomicInteger treeRecursiveEnterCount = new AtomicInteger(0); static AtomicInteger treeRecursiveExitCount = new AtomicInteger(0); static AtomicInteger treeRecursiveRemainCount = new AtomicInteger(0); static AtomicInteger treeRecursiveCount = new AtomicInteger(0); static Timer timer = new Timer(); public static void main(String[] args) { //構造一顆樹 TreeNode rootNode = buildTree(); //列印樹 System.out.println("==================printTree=================="); printTree(rootNode); //展開回呼(若異步退出需寫在異步函式里) System.out.println("==================expandTree=================="); expandTree(rootNode, new Callback() { @Override public void callback() { System.out.println("callback execute"); //結束時間器 timer.cancel(); } }); } private static TreeNode buildTree() { TreeNode rootNode = new TreeNode(); rootNode.setId(1); rootNode.setLevel(0); TreeNode treeNode21 = new TreeNode(); treeNode21.setId(2); treeNode21.setLevel(1); rootNode.getChildren().add(treeNode21); TreeNode treeNode211 = new TreeNode(); treeNode211.setId(4); treeNode211.setLevel(2); treeNode21.getChildren().add(treeNode211); TreeNode treeNode22 = new TreeNode(); treeNode22.setId(3); treeNode22.setLevel(1); rootNode.getChildren().add(treeNode22); return rootNode; } /** * 列印樹 * * @param node */ static void printTree(TreeNode node) { System.out.println(node); if (!node.getChildren().isEmpty()) { for (TreeNode childNode : node.getChildren()) { printTree(childNode); } } } static void expandTree(TreeNode node, Callback callback) { //進入遞回函式,統計遞回進入次數 treeRecursiveCount.incrementAndGet(); treeRecursiveEnterCount.incrementAndGet(); treeRecursiveRemainCount.incrementAndGet(); printRecursiveCounts(); //展開結點(模擬異步) timer.schedule(new TimerTask() { @Override public void run() { System.out.println("展開結點:" + node); //遞回孩子 if (!node.getChildren().isEmpty()) { for (TreeNode childNode : node.getChildren()) { expandTree(childNode, callback); } treeRecursiveExitCount.incrementAndGet(); treeRecursiveRemainCount.decrementAndGet(); printRecursiveCounts(); if (treeRecursiveRemainCount.get() == 0) { callback.callback(); } } else { treeRecursiveExitCount.incrementAndGet(); treeRecursiveRemainCount.decrementAndGet(); printRecursiveCounts(); if (treeRecursiveRemainCount.get() == 0) { callback.callback(); } } } }, 1000); } private static void printRecursiveCounts() { System.out.printf("遞回次數:%d,進入次數:%d,退出次數:%d,剩余次數:%d\n", treeRecursiveCount.get(), treeRecursiveEnterCount.get(), treeRecursiveExitCount.get(), treeRecursiveRemainCount.get()); } }
4.測驗結果
==================printTree==================
TreeNode{id=1, level=0}
TreeNode{id=2, level=1}
TreeNode{id=4, level=2}
TreeNode{id=3, level=1}
==================expandTree==================
遞回次數:1,進入次數:1,退出次數:0,剩余次數:1
展開結點:TreeNode{id=1, level=0}
遞回次數:2,進入次數:2,退出次數:0,剩余次數:2
遞回次數:3,進入次數:3,退出次數:0,剩余次數:3
遞回次數:3,進入次數:3,退出次數:1,剩余次數:2
展開結點:TreeNode{id=2, level=1}
遞回次數:4,進入次數:4,退出次數:1,剩余次數:3
遞回次數:4,進入次數:4,退出次數:2,剩余次數:2
展開結點:TreeNode{id=3, level=1}
遞回次數:4,進入次數:4,退出次數:3,剩余次數:1
展開結點:TreeNode{id=4, level=2}
遞回次數:4,進入次數:4,退出次數:4,剩余次數:0
callback execute
javascript實作版
下面是對 vue框架element-ui庫中的tree組件 實作的展開回呼,實作功能:展開異步樹指定結點然后勾選結點
//展開匹配的結點,根結點默認展開 expandNodes(nodeDataIds){ let that = this; let tree = this.$refs.tree; let rootNode = tree.root; this.expandNode(rootNode, nodeDataIds, function(){ that.checkNodes(['node001','node002']); }); }, //展開指定結點下匹配的結點 expandNode(node, nodeDataIds, callback){ let that = this; //遞回進入 that.recursiveEnter(); //當前結點需要展開未展開,則展開(根結點默認展開) if(node.level==0 || nodeDataIds.indexOf(node.data.id) != -1){ //展開孩子結點 let expandChildren = function(){ let childNodes = node.childNodes; if(childNodes.length > 0){ for (let i = 0; i < childNodes.length; i++) { let childNode = childNodes[i]; //遞回展開孩子結點 that.expandNode(childNode, nodeDataIds, callback); } } } if(!node.expanded){ //當前結點未展開則先展開,展開后再展開孩子結點 node.expand(function(){ //展開孩子結點 expandChildren(); //遞回退出 that.recursiveExit(callback); }); }else{ //當前結點已展開,直接展開孩子結點 expandChildren(); //遞回退出 that.recursiveExit(callback); } }else{ //遞回退出 that.recursiveExit(callback); } }, //遞回計入計數剩余遞回次數 recursiveEnter(){ this.recursiveRemainCount++; console.log('enter recursiveRemainCount', this.recursiveRemainCount) }, //遞回退出計數剩余遞回次數 recursiveExit(callback){ this.recursiveRemainCount--; console.log('exit recursiveRemainCount', this.recursiveRemainCount) if(this.recursiveRemainCount==0){ if(callback){ callback(); } } }, checkNodes(nodeDataIds){ let tree = this.$refs.tree; tree.setCheckedKeys(nodeDataIds, false) }
總結
遞回回呼的實作,可以用一個變數統計剩余遞回次數,當剩余遞回次數為0時,則表示遞回結束,可以執行回呼函式,前端js是單執行緒,無需考慮執行緒安全問題,后端則需要考慮執行緒安全問題,java可以用原子類AtomicInteger統計次數,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/501798.html
標籤:其他
上一篇:看了1000+份簡歷,我蚌埠住了
