如何避免“超出最大调用堆栈大小”异常?

时间:2017-02-24 13:40:49

标签: javascript c# recursion

我有下一个按网址列表生成站点地图树的代码。 C#型号:

public class Node
{
    public Node(string child, string parent)
    {
        Child = child;
        Parent = parent;
    }
    public string Parent { get; set; }
    public string Child { get; set; }
    public bool IsRoot { get; set; }
}

生成节点列表的C#方法。

private static List<Node> ExtractNode(List<string> Urls)
    {
        List<Node> nodeList = new List<Node>();

        foreach (string itm in Urls)
        {
            string[] arr = itm.Split('/');
            int index = -1;

            foreach (string node in arr)
            {
                index += 1;
                if (index == 0)
                {
                    Node rootNode = new Node(node, "");
                    if (!nodeList.Exists(x => x.Child == rootNode.Child & x.Parent == rootNode.Parent))
                    {
                        rootNode.IsRoot = true;
                        nodeList.Add(rootNode);
                    }
                }
                else
                {
                    Node childNode = new Node(node, arr[index - 1].ToString());
                    {
                        if (!nodeList.Exists(x => x.Child == childNode.Child & x.Parent == childNode.Parent))
                        {
                            nodeList.Add(childNode);
                        }
                    }
                }
            }
        }
        return nodeList;
    }

Javascript代码。下一个函数获取节点列表:

function makeTree(nodes) {
var roots = [];
for (var i = 0; i < nodes.length; i++) {
    if (nodes[i].IsRoot) {
        roots.push(nodes[i].Child);
    }
}
var trees = "";
for (var j = 0; j < roots.length; j++) {
    trees += "<div class='sitemapRoot'><ul><li>" + getChildren(roots[j], nodes) + "</li></ul></div>";
}
return trees;
}

接下来一个叫递归:

function getChildren(root, nodes) {
var result = "";
var index = 0;
for (var i = 0; i < nodes.length; i++) {
    if (nodes[i].Parent == root) {
        index += 1;
    }
}

if (index > 0) {
    var RootHeader = "";
    for (var j = 0; j < nodes.length; j++) {
        if (nodes[j].IsRoot & root == nodes[j].Child) {
            RootHeader = nodes[j].Child;
        }
    }
    result += RootHeader + "<ul>\n";
    for (var k = 0; k < nodes.length; k++) {
        if (nodes[k].IsRoot & root == nodes[k].Child) {
            RootHeader = nodes[k].Child;
        }
        if (nodes[k].Parent == root) {
            result += "<ul><li>" + nodes[k].Child + getChildren(nodes[k].Child, nodes) + "</li></ul>\n";
        }
    }
    result += "</ul>\n";
    return result;
}
return "";
}

此代码适用于一小组数据。但是当我尝试传递500个节点的列表时,我得到下一个错误:

  

未捕获RangeError:超出最大调用堆栈大小   在getChildren(treeGenerator.js:371)

那么,问题是如何优化代码以避免此错误?

1 个答案:

答案 0 :(得分:1)

有两种方法可以解决此问题。使用递归函数时,总是要担心调用堆栈大小。如果您认为这是一个问题,那么您需要进行异步或重构而不是递归。这些不一定是最优化的答案,但希望它们能让你开始。

非递归函数

function makeTree(nodes) {
    var roots = [],
        limbs = [],
        i, j;

    //go through all of the nodes and link the parent/children.
    for (i = 0; i < nodes.length; i++) {
        for (j = i + 1; j < nodes.length; j++) {//only look at the rest of the elements to increase performance
            if (nodes[i].Child == nodes[j].Parent) {
                nodes[i].children = (nodes[i].children || []);
                nodes[i].children.push(nodes[j]);
                nodes[j].parentNode = nodes[i];
            }
        }
        for (j = 0; j < limbs.length; j++) {//look through the limbs to see if one of them is my child.
            if (nodes[i].Child == limbs[j].Parent) {
                nodes[i].children = (nodes[i].children || []);
                nodes[i].children.push(limbs[j]);
                limbs[j].parentNode = nodes[i];
                limbs.splice(j--, 1);//remove from the list since I can only have 1 parent.
            }
        }
        //I have all of my children.
        if (nodes[i].IsRoot) {
            roots.push(nodes[i]);
        }else if(!nodes[i].parentNode){
            //I don't know my parent so I'm a limb.
            limbs.push(node);
        }

    }
    //now that everyone knows their parent and their children, we'll assemble the html by looking at all of the leafs first and working way up the tree.
    i = 0;
    while(nodes.length > 0){
        if(!nodes[i].children || nodes[i].childHtml){
            //I'm either a leaf or I have my child's html.
            var node = nodes[i];
            node.html = node.Child + (node.childHtml? "<ul>" + node.childHtml + "</ul>" : "");
            node.childHtml = null;//don't need this anymore.
            if(node.parentNode){
                //let's check to see if my siblings are complete
                var ready = true,
                    childHtml = "";
                for(var j = 0; j < node.parentNode.children.length; j++){
                    if(node.parentNode.children[j].html == null){
                        ready = false;//I don't know this html yet so skip it for now.
                        break;
                    }else{
                        childHtml += "<li>" + node.parentNode.children[j].html + "</li>";//go ahead and try to create the list of children.
                    }
                }
                if(ready){
                    //all of siblings are complete, so update parent.
                    node.parentNode.childHtml = childHtml;
                    node.parentNode.children = null;//remove reference for cleanup.
                }
            }
            nodes.splice(i, 1);//remove from the list since I have my html.
        }else{
            i++;//look at the next node in the list.
        }
        if(i >= nodes.length){
            i = 0;//since we are splicing and going through the list multiple times (possibly), we'll set the index back to 0.
        }
    }

    //every node knows it's html, so build the full tree.
    var trees = "";
    for (var j = 0; j < roots.length; j++) {
        trees += "<div class='sitemapRoot'><ul><li>" + roots[j].html + "</li></ul></div>";
    }
    return trees;
}

异步递归函数

function makeTreeAsync(nodes, callback) {
    var roots = [],
        numRoots = 0;
    for (var i = 0; i < nodes.length; i++) {
        if (nodes[i].IsRoot) {
            numRoots++;
            getChildrenAsync(nodes[i], nodes, create);
        }
    }
    function create(child){
        roots.push(child);
        if(roots.length === numRoots){
            var trees = "";
            for (var j = 0; j < roots.length; j++) {
                trees += "<div class='sitemapRoot'><ul><li>" + roots[j].html + "</li></ul></div>";
            }
            callback(trees);
        }
    }   
}
function getChildrenAsync(root, nodes, callback) {
    var result = "";
    var index = 0;
    var children = [];
    for (var i = 0; i < nodes.length; i++) {
        if (nodes[i].Parent == root.Child) {
            index += 1;
            getChild(i);
        }
    }

    if (index == 0) {
        root.html = root.Child;
        callback(root);
    }
    function getChild(x){
        setTimeout(function(){
            getChildrenAsync(nodes[x], nodes, createHtml);
        });
    }
    function createHtml(node){
        children.push(node);
        if(children.length === index){      
            var result = root.Child;
            if(children.length){
                result += "<ul>"
                for (var j = 0; j < children.length; j++) {
                    result += "<li>" + children[j].html + "</li>";
                }
                result += "</ul>";
            }
            root.html = result;
            callback(root);
        }
    }
}

我使用以下内容创建用于测试代码的树:

function makeTestNodes(numRoots, numChildrenPerRoot){
    var nodes= [];
    for(var i = 0;i < numRoots;i++){
        nodes.push({
            Parent: "",
            Child: i.toString(),
            IsRoot: 1
        });
        var parent = i.toString();
        for(var j = 0;j < numChildrenPerRoot;j++){
            var child = parent + "." + j.toString();
            nodes.push({
                Parent: parent,
                Child: child,
                IsRoot: 0
            });
            nodes.push({
                Parent: parent,
                Child: parent + "." + j.toString() + "a",
                IsRoot: 0
            });
            parent = child;
        }
    }
    return nodes;
}

使用以下代码段调用测试节点的两个函数: 注意:以下使用jquery来获取body节点。当页面准备就绪时,我调用了以下代码段。

var body = $("body");
body.append(makeTree(makeTestNodes(20, 50)));

makeTreeAsync(makeTestNodes(20, 50), function(html){
    body.append(html);
});